summaryrefslogtreecommitdiffstats
path: root/gfx/layers/wr
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/layers/wr')
-rw-r--r--gfx/layers/wr/AsyncImagePipelineManager.cpp801
-rw-r--r--gfx/layers/wr/AsyncImagePipelineManager.h296
-rw-r--r--gfx/layers/wr/AsyncImagePipelineOp.cpp30
-rw-r--r--gfx/layers/wr/AsyncImagePipelineOp.h73
-rw-r--r--gfx/layers/wr/ClipManager.cpp503
-rw-r--r--gfx/layers/wr/ClipManager.h150
-rw-r--r--gfx/layers/wr/DisplayItemCache.cpp203
-rw-r--r--gfx/layers/wr/DisplayItemCache.h210
-rw-r--r--gfx/layers/wr/HitTestInfoManager.cpp134
-rw-r--r--gfx/layers/wr/HitTestInfoManager.h66
-rw-r--r--gfx/layers/wr/IpcResourceUpdateQueue.cpp484
-rw-r--r--gfx/layers/wr/IpcResourceUpdateQueue.h195
-rw-r--r--gfx/layers/wr/OMTAController.cpp41
-rw-r--r--gfx/layers/wr/OMTAController.h44
-rw-r--r--gfx/layers/wr/OMTASampler.cpp248
-rw-r--r--gfx/layers/wr/OMTASampler.h156
-rw-r--r--gfx/layers/wr/RenderRootStateManager.cpp210
-rw-r--r--gfx/layers/wr/RenderRootStateManager.h97
-rw-r--r--gfx/layers/wr/RenderRootTypes.cpp108
-rw-r--r--gfx/layers/wr/RenderRootTypes.h74
-rw-r--r--gfx/layers/wr/StackingContextHelper.cpp276
-rw-r--r--gfx/layers/wr/StackingContextHelper.h129
-rw-r--r--gfx/layers/wr/WebRenderBridgeChild.cpp586
-rw-r--r--gfx/layers/wr/WebRenderBridgeChild.h261
-rw-r--r--gfx/layers/wr/WebRenderBridgeParent.cpp2886
-rw-r--r--gfx/layers/wr/WebRenderBridgeParent.h527
-rw-r--r--gfx/layers/wr/WebRenderCanvasRenderer.cpp94
-rw-r--r--gfx/layers/wr/WebRenderCanvasRenderer.h57
-rw-r--r--gfx/layers/wr/WebRenderCommandBuilder.cpp2972
-rw-r--r--gfx/layers/wr/WebRenderCommandBuilder.h237
-rw-r--r--gfx/layers/wr/WebRenderDrawEventRecorder.cpp33
-rw-r--r--gfx/layers/wr/WebRenderDrawEventRecorder.h53
-rw-r--r--gfx/layers/wr/WebRenderImageHost.cpp385
-rw-r--r--gfx/layers/wr/WebRenderImageHost.h92
-rw-r--r--gfx/layers/wr/WebRenderLayerManager.cpp803
-rw-r--r--gfx/layers/wr/WebRenderLayerManager.h281
-rw-r--r--gfx/layers/wr/WebRenderMessageUtils.h230
-rw-r--r--gfx/layers/wr/WebRenderScrollData.cpp502
-rw-r--r--gfx/layers/wr/WebRenderScrollData.h358
-rw-r--r--gfx/layers/wr/WebRenderScrollDataWrapper.h536
-rw-r--r--gfx/layers/wr/WebRenderTextureHost.cpp219
-rw-r--r--gfx/layers/wr/WebRenderTextureHost.h113
-rw-r--r--gfx/layers/wr/WebRenderUserData.cpp427
-rw-r--r--gfx/layers/wr/WebRenderUserData.h386
44 files changed, 16566 insertions, 0 deletions
diff --git a/gfx/layers/wr/AsyncImagePipelineManager.cpp b/gfx/layers/wr/AsyncImagePipelineManager.cpp
new file mode 100644
index 0000000000..464b8fb69f
--- /dev/null
+++ b/gfx/layers/wr/AsyncImagePipelineManager.cpp
@@ -0,0 +1,801 @@
+/* -*- 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 "AsyncImagePipelineManager.h"
+
+#include <algorithm>
+#include <iterator>
+
+#include "CompositableHost.h"
+#include "gfxEnv.h"
+#include "mozilla/gfx/gfxVars.h"
+#include "mozilla/layers/AsyncImagePipelineOp.h"
+#include "mozilla/layers/CompositorThread.h"
+#include "mozilla/layers/RemoteTextureHostWrapper.h"
+#include "mozilla/layers/SharedSurfacesParent.h"
+#include "mozilla/layers/WebRenderImageHost.h"
+#include "mozilla/layers/WebRenderTextureHost.h"
+#include "mozilla/webrender/RenderThread.h"
+#include "mozilla/webrender/WebRenderAPI.h"
+#include "mozilla/webrender/WebRenderTypes.h"
+
+#ifdef MOZ_WIDGET_ANDROID
+# include "mozilla/layers/TextureHostOGL.h"
+#endif
+
+namespace mozilla {
+namespace layers {
+
+AsyncImagePipelineManager::ForwardingExternalImage::~ForwardingExternalImage() {
+ DebugOnly<bool> released = SharedSurfacesParent::Release(mImageId);
+ MOZ_ASSERT(released);
+}
+
+AsyncImagePipelineManager::AsyncImagePipeline::AsyncImagePipeline(
+ wr::PipelineId aPipelineId, layers::WebRenderBackend aBackend)
+ : mInitialised(false),
+ mIsChanged(false),
+ mUseExternalImage(false),
+ mRotation(wr::WrRotation::Degree0),
+ mFilter(wr::ImageRendering::Auto),
+ mMixBlendMode(wr::MixBlendMode::Normal),
+ mDLBuilder(aPipelineId, aBackend) {}
+
+AsyncImagePipelineManager::AsyncImagePipelineManager(
+ RefPtr<wr::WebRenderAPI>&& aApi, bool aUseCompositorWnd)
+ : mApi(aApi),
+ mUseCompositorWnd(aUseCompositorWnd),
+ mIdNamespace(mApi->GetNamespace()),
+ mUseTripleBuffering(mApi->GetUseTripleBuffering()),
+ mResourceId(0),
+ mAsyncImageEpoch{0},
+ mWillGenerateFrame(false),
+ mDestroyed(false),
+#ifdef XP_WIN
+ mUseWebRenderDCompVideoHwOverlayWin(
+ gfx::gfxVars::UseWebRenderDCompVideoHwOverlayWin()),
+ mUseWebRenderDCompVideoSwOverlayWin(
+ gfx::gfxVars::UseWebRenderDCompVideoSwOverlayWin()),
+#endif
+ mRenderSubmittedUpdatesLock("SubmittedUpdatesLock"),
+ mLastCompletedFrameId(0) {
+ MOZ_COUNT_CTOR(AsyncImagePipelineManager);
+}
+
+AsyncImagePipelineManager::~AsyncImagePipelineManager() {
+ MOZ_COUNT_DTOR(AsyncImagePipelineManager);
+}
+
+void AsyncImagePipelineManager::Destroy() {
+ MOZ_ASSERT(!mDestroyed);
+ mApi = nullptr;
+ mPipelineTexturesHolders.Clear();
+ mDestroyed = true;
+}
+
+/* static */
+wr::ExternalImageId AsyncImagePipelineManager::GetNextExternalImageId() {
+ static std::atomic<uint64_t> sCounter = 0;
+
+ uint64_t id = ++sCounter;
+ // Upper 32bit(namespace) needs to be 0.
+ // Namespace other than 0 might be used by others.
+ MOZ_RELEASE_ASSERT(id != UINT32_MAX);
+ return wr::ToExternalImageId(id);
+}
+
+void AsyncImagePipelineManager::SetWillGenerateFrame() {
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+
+ mWillGenerateFrame = true;
+}
+
+bool AsyncImagePipelineManager::GetAndResetWillGenerateFrame() {
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+
+ bool ret = mWillGenerateFrame;
+ mWillGenerateFrame = false;
+ return ret;
+}
+
+void AsyncImagePipelineManager::AddPipeline(const wr::PipelineId& aPipelineId,
+ WebRenderBridgeParent* aWrBridge) {
+ if (mDestroyed) {
+ return;
+ }
+
+ mPipelineTexturesHolders.WithEntryHandle(
+ wr::AsUint64(aPipelineId), [&](auto&& holder) {
+ if (holder) {
+ // This could happen during tab move between different windows.
+ // Previously removed holder could be still alive for waiting
+ // destroyed.
+ MOZ_ASSERT(holder.Data()->mDestroyedEpoch.isSome());
+ holder.Data()->mDestroyedEpoch = Nothing(); // Revive holder
+ holder.Data()->mWrBridge = aWrBridge;
+ return;
+ }
+
+ holder.Insert(MakeUnique<PipelineTexturesHolder>())->mWrBridge =
+ aWrBridge;
+ });
+}
+
+void AsyncImagePipelineManager::RemovePipeline(
+ const wr::PipelineId& aPipelineId, const wr::Epoch& aEpoch) {
+ if (mDestroyed) {
+ return;
+ }
+
+ PipelineTexturesHolder* holder =
+ mPipelineTexturesHolders.Get(wr::AsUint64(aPipelineId));
+ MOZ_ASSERT(holder);
+ if (!holder) {
+ return;
+ }
+ holder->mWrBridge = nullptr;
+ holder->mDestroyedEpoch = Some(aEpoch);
+}
+
+WebRenderBridgeParent* AsyncImagePipelineManager::GetWrBridge(
+ const wr::PipelineId& aPipelineId) {
+ if (mDestroyed) {
+ return nullptr;
+ }
+
+ PipelineTexturesHolder* holder =
+ mPipelineTexturesHolders.Get(wr::AsUint64(aPipelineId));
+ if (!holder) {
+ return nullptr;
+ }
+ if (holder->mWrBridge) {
+ MOZ_ASSERT(holder->mDestroyedEpoch.isNothing());
+ return holder->mWrBridge;
+ }
+
+ return nullptr;
+}
+
+void AsyncImagePipelineManager::AddAsyncImagePipeline(
+ const wr::PipelineId& aPipelineId, WebRenderImageHost* aImageHost) {
+ if (mDestroyed) {
+ return;
+ }
+ MOZ_ASSERT(aImageHost);
+ uint64_t id = wr::AsUint64(aPipelineId);
+
+ MOZ_ASSERT(!mAsyncImagePipelines.Contains(id));
+ auto holder =
+ MakeUnique<AsyncImagePipeline>(aPipelineId, mApi->GetBackendType());
+ holder->mImageHost = aImageHost;
+ mAsyncImagePipelines.InsertOrUpdate(id, std::move(holder));
+ AddPipeline(aPipelineId, /* aWrBridge */ nullptr);
+}
+
+void AsyncImagePipelineManager::RemoveAsyncImagePipeline(
+ const wr::PipelineId& aPipelineId, wr::TransactionBuilder& aTxn) {
+ if (mDestroyed) {
+ return;
+ }
+
+ uint64_t id = wr::AsUint64(aPipelineId);
+ if (auto entry = mAsyncImagePipelines.Lookup(id)) {
+ const auto& holder = entry.Data();
+ wr::Epoch epoch = GetNextImageEpoch();
+ aTxn.ClearDisplayList(epoch, aPipelineId);
+ for (wr::ImageKey key : holder->mKeys) {
+ aTxn.DeleteImage(key);
+ }
+ entry.Remove();
+ RemovePipeline(aPipelineId, epoch);
+ }
+}
+
+void AsyncImagePipelineManager::UpdateAsyncImagePipeline(
+ const wr::PipelineId& aPipelineId, const LayoutDeviceRect& aScBounds,
+ const wr::WrRotation aRotation, const wr::ImageRendering& aFilter,
+ const wr::MixBlendMode& aMixBlendMode) {
+ if (mDestroyed) {
+ return;
+ }
+ AsyncImagePipeline* pipeline =
+ mAsyncImagePipelines.Get(wr::AsUint64(aPipelineId));
+ if (!pipeline) {
+ return;
+ }
+ pipeline->mInitialised = true;
+ pipeline->Update(aScBounds, aRotation, aFilter, aMixBlendMode);
+}
+
+Maybe<TextureHost::ResourceUpdateOp> AsyncImagePipelineManager::UpdateImageKeys(
+ const wr::Epoch& aEpoch, const wr::PipelineId& aPipelineId,
+ AsyncImagePipeline* aPipeline, TextureHost* aTexture,
+ nsTArray<wr::ImageKey>& aKeys, wr::TransactionBuilder& aSceneBuilderTxn,
+ wr::TransactionBuilder& aMaybeFastTxn) {
+ MOZ_ASSERT(aKeys.IsEmpty());
+ MOZ_ASSERT(aPipeline);
+
+ TextureHost* previousTexture = aPipeline->mCurrentTexture.get();
+
+ if (aTexture == previousTexture) {
+ // The texture has not changed, just reuse previous ImageKeys.
+ aKeys = aPipeline->mKeys.Clone();
+ return Nothing();
+ }
+
+ auto* wrapper = aTexture ? aTexture->AsRemoteTextureHostWrapper() : nullptr;
+ if (wrapper && !aPipeline->mImageHost->GetAsyncRef()) {
+ std::function<void(const RemoteTextureInfo&)> function;
+ RemoteTextureMap::Get()->GetRemoteTexture(
+ wrapper, std::move(function), /* aWaitForRemoteTextureOwner */ false);
+ }
+
+ if (!aTexture || aTexture->NumSubTextures() == 0) {
+ // We don't have a new texture or texture does not have SubTextures, there
+ // isn't much we can do.
+ aKeys = aPipeline->mKeys.Clone();
+ return Nothing();
+ }
+
+ aPipeline->mCurrentTexture = aTexture;
+
+ WebRenderTextureHost* wrTexture = aTexture->AsWebRenderTextureHost();
+ MOZ_ASSERT(wrTexture);
+ if (!wrTexture) {
+ gfxCriticalNote << "WebRenderTextureHost is not used";
+ }
+
+ bool useExternalImage = !!wrTexture;
+ aPipeline->mUseExternalImage = useExternalImage;
+
+ // The non-external image code path falls back to converting the texture into
+ // an rgb image.
+ auto numKeys = useExternalImage ? aTexture->NumSubTextures() : 1;
+ MOZ_ASSERT(numKeys > 0);
+
+ // If we already had a texture and the format hasn't changed, better to reuse
+ // the image keys than create new ones.
+ auto backend = aSceneBuilderTxn.GetBackendType();
+ bool canUpdate =
+ !!previousTexture &&
+ previousTexture->GetTextureHostType() == aTexture->GetTextureHostType() &&
+ previousTexture->GetSize() == aTexture->GetSize() &&
+ previousTexture->GetFormat() == aTexture->GetFormat() &&
+ previousTexture->GetColorDepth() == aTexture->GetColorDepth() &&
+ previousTexture->NeedsYFlip() == aTexture->NeedsYFlip() &&
+ previousTexture->SupportsExternalCompositing(backend) ==
+ aTexture->SupportsExternalCompositing(backend) &&
+ aPipeline->mKeys.Length() == numKeys;
+
+ if (!canUpdate) {
+ for (auto key : aPipeline->mKeys) {
+ // Destroy ImageKeys on transaction of scene builder thread, since
+ // DisplayList is updated on SceneBuilder thread. It prevents too early
+ // ImageKey deletion.
+ aSceneBuilderTxn.DeleteImage(key);
+ }
+ aPipeline->mKeys.Clear();
+ for (uint32_t i = 0; i < numKeys; ++i) {
+ aPipeline->mKeys.AppendElement(GenerateImageKey());
+ }
+ }
+
+ aKeys = aPipeline->mKeys.Clone();
+
+ auto op = canUpdate ? TextureHost::UPDATE_IMAGE : TextureHost::ADD_IMAGE;
+
+ if (!useExternalImage) {
+ return UpdateWithoutExternalImage(aTexture, aKeys[0], op, aMaybeFastTxn);
+ }
+
+ wrTexture->MaybeNotifyForUse(aMaybeFastTxn);
+
+ Range<wr::ImageKey> keys(&aKeys[0], aKeys.Length());
+ auto externalImageKey = wrTexture->GetExternalImageKey();
+ wrTexture->PushResourceUpdates(aMaybeFastTxn, op, keys, externalImageKey);
+
+ return Some(op);
+}
+
+Maybe<TextureHost::ResourceUpdateOp>
+AsyncImagePipelineManager::UpdateWithoutExternalImage(
+ TextureHost* aTexture, wr::ImageKey aKey, TextureHost::ResourceUpdateOp aOp,
+ wr::TransactionBuilder& aTxn) {
+ MOZ_ASSERT(aTexture);
+
+ RefPtr<gfx::DataSourceSurface> dSurf = aTexture->GetAsSurface();
+ if (!dSurf) {
+ NS_ERROR("TextureHost does not return DataSourceSurface");
+ return Nothing();
+ }
+ gfx::DataSourceSurface::MappedSurface map;
+ if (!dSurf->Map(gfx::DataSourceSurface::MapType::READ, &map)) {
+ NS_ERROR("DataSourceSurface failed to map");
+ return Nothing();
+ }
+
+ gfx::IntSize size = dSurf->GetSize();
+ wr::ImageDescriptor descriptor(size, map.mStride, dSurf->GetFormat());
+
+ // Costly copy right here...
+ wr::Vec<uint8_t> bytes;
+ bytes.PushBytes(Range<uint8_t>(map.mData, size.height * map.mStride));
+
+ if (aOp == TextureHost::UPDATE_IMAGE) {
+ aTxn.UpdateImageBuffer(aKey, descriptor, bytes);
+ } else {
+ aTxn.AddImage(aKey, descriptor, bytes);
+ }
+
+ dSurf->Unmap();
+
+ return Some(aOp);
+}
+
+void AsyncImagePipelineManager::ApplyAsyncImagesOfImageBridge(
+ wr::TransactionBuilder& aSceneBuilderTxn,
+ wr::TransactionBuilder& aFastTxn) {
+ if (mDestroyed || mAsyncImagePipelines.Count() == 0) {
+ return;
+ }
+
+#ifdef XP_WIN
+ // UseWebRenderDCompVideoHwOverlayWin() and
+ // UseWebRenderDCompVideoSwOverlayWin() could be changed from true to false,
+ // when DCompVideoOverlay task is failed. In this case, DisplayItems need to
+ // be re-pushed to WebRender for disabling video overlay.
+ bool isChanged = (mUseWebRenderDCompVideoHwOverlayWin !=
+ gfx::gfxVars::UseWebRenderDCompVideoHwOverlayWin()) ||
+ (mUseWebRenderDCompVideoSwOverlayWin !=
+ gfx::gfxVars::UseWebRenderDCompVideoSwOverlayWin());
+
+ if (isChanged) {
+ mUseWebRenderDCompVideoHwOverlayWin =
+ gfx::gfxVars::UseWebRenderDCompVideoHwOverlayWin();
+ mUseWebRenderDCompVideoSwOverlayWin =
+ gfx::gfxVars::UseWebRenderDCompVideoSwOverlayWin();
+ }
+#endif
+
+ wr::Epoch epoch = GetNextImageEpoch();
+
+ // We use a pipeline with a very small display list for each video element.
+ // Update each of them if needed.
+ for (const auto& entry : mAsyncImagePipelines) {
+ wr::PipelineId pipelineId = wr::AsPipelineId(entry.GetKey());
+ AsyncImagePipeline* pipeline = entry.GetWeak();
+
+#ifdef XP_WIN
+ if (isChanged) {
+ pipeline->mIsChanged = true;
+ }
+#endif
+
+ // If aync image pipeline does not use ImageBridge, do not need to apply.
+ if (!pipeline->mImageHost->GetAsyncRef()) {
+ continue;
+ }
+ TextureHost* texture =
+ pipeline->mImageHost->GetAsTextureHostForComposite(this);
+
+ ApplyAsyncImageForPipeline(epoch, pipelineId, pipeline, texture,
+ aSceneBuilderTxn, aFastTxn);
+ }
+}
+
+void AsyncImagePipelineManager::ApplyAsyncImageForPipeline(
+ const wr::Epoch& aEpoch, const wr::PipelineId& aPipelineId,
+ AsyncImagePipeline* aPipeline, TextureHost* aTexture,
+ wr::TransactionBuilder& aSceneBuilderTxn,
+ wr::TransactionBuilder& aMaybeFastTxn) {
+ nsTArray<wr::ImageKey> keys;
+ auto op = UpdateImageKeys(aEpoch, aPipelineId, aPipeline, aTexture, keys,
+ aSceneBuilderTxn, aMaybeFastTxn);
+
+ bool updateDisplayList =
+ aPipeline->mInitialised &&
+ (aPipeline->mIsChanged || op == Some(TextureHost::ADD_IMAGE)) &&
+ !!aPipeline->mCurrentTexture;
+
+ if (!updateDisplayList) {
+ // We don't need to update the display list, either because we can't or
+ // because the previous one is still up to date. We may, however, have
+ // updated some resources.
+
+ // Use transaction of scene builder thread to notify epoch.
+ // It is for making epoch update consistent.
+ aSceneBuilderTxn.UpdateEpoch(aPipelineId, aEpoch);
+ if (aPipeline->mCurrentTexture) {
+ HoldExternalImage(aPipelineId, aEpoch, aPipeline->mCurrentTexture);
+ }
+ return;
+ }
+
+ aPipeline->mIsChanged = false;
+ aPipeline->mDLBuilder.Begin();
+
+ float opacity = 1.0f;
+ wr::StackingContextParams params;
+ params.opacity = &opacity;
+ params.mix_blend_mode = aPipeline->mMixBlendMode;
+
+ wr::WrComputedTransformData computedTransform;
+ computedTransform.vertical_flip =
+ aPipeline->mCurrentTexture && aPipeline->mCurrentTexture->NeedsYFlip();
+ computedTransform.scale_from = {
+ float(aPipeline->mCurrentTexture->GetSize().width),
+ float(aPipeline->mCurrentTexture->GetSize().height)};
+ computedTransform.rotation = aPipeline->mRotation;
+ // We don't have a frame / per-frame key here, but we can use the pipeline id
+ // and the key kind to create a unique stable key.
+ computedTransform.key = wr::SpatialKey(
+ aPipelineId.mNamespace, aPipelineId.mHandle, wr::SpatialKeyKind::APZ);
+ params.computed_transform = &computedTransform;
+
+ Maybe<wr::WrSpatialId> referenceFrameId =
+ aPipeline->mDLBuilder.PushStackingContext(
+ params, wr::ToLayoutRect(aPipeline->mScBounds),
+ // This is fine to do unconditionally because we only push images
+ // here.
+ wr::RasterSpace::Screen());
+
+ Maybe<wr::SpaceAndClipChainHelper> spaceAndClipChainHelper;
+ if (referenceFrameId) {
+ spaceAndClipChainHelper.emplace(aPipeline->mDLBuilder,
+ referenceFrameId.ref());
+ }
+
+ if (aPipeline->mCurrentTexture && !keys.IsEmpty()) {
+ LayoutDeviceRect rect(0, 0, aPipeline->mCurrentTexture->GetSize().width,
+ aPipeline->mCurrentTexture->GetSize().height);
+
+ if (aPipeline->mUseExternalImage) {
+ MOZ_ASSERT(aPipeline->mCurrentTexture->AsWebRenderTextureHost());
+ Range<wr::ImageKey> range_keys(&keys[0], keys.Length());
+ TextureHost::PushDisplayItemFlagSet flags;
+ flags += TextureHost::PushDisplayItemFlag::PREFER_COMPOSITOR_SURFACE;
+ if (mApi->SupportsExternalBufferTextures()) {
+ flags +=
+ TextureHost::PushDisplayItemFlag::SUPPORTS_EXTERNAL_BUFFER_TEXTURES;
+ }
+ aPipeline->mCurrentTexture->PushDisplayItems(
+ aPipeline->mDLBuilder, wr::ToLayoutRect(rect), wr::ToLayoutRect(rect),
+ aPipeline->mFilter, range_keys, flags);
+ HoldExternalImage(aPipelineId, aEpoch, aPipeline->mCurrentTexture);
+ } else {
+ MOZ_ASSERT(keys.Length() == 1);
+ aPipeline->mDLBuilder.PushImage(wr::ToLayoutRect(rect),
+ wr::ToLayoutRect(rect), true, false,
+ aPipeline->mFilter, keys[0]);
+ }
+ }
+
+ spaceAndClipChainHelper.reset();
+ aPipeline->mDLBuilder.PopStackingContext(referenceFrameId.isSome());
+
+ wr::BuiltDisplayList dl;
+ aPipeline->mDLBuilder.End(dl);
+ aSceneBuilderTxn.SetDisplayList(aEpoch, aPipelineId, dl.dl_desc, dl.dl_items,
+ dl.dl_cache, dl.dl_spatial_tree);
+}
+
+void AsyncImagePipelineManager::ApplyAsyncImageForPipeline(
+ const wr::PipelineId& aPipelineId, wr::TransactionBuilder& aTxn,
+ wr::TransactionBuilder& aTxnForImageBridge,
+ AsyncImagePipelineOps* aPendingOps,
+ RemoteTextureInfoList* aPendingRemoteTextures) {
+ AsyncImagePipeline* pipeline =
+ mAsyncImagePipelines.Get(wr::AsUint64(aPipelineId));
+ if (!pipeline) {
+ return;
+ }
+
+ // ready event of RemoteTexture that uses ImageBridge do not need to be
+ // checked here.
+ if (pipeline->mImageHost->GetAsyncRef()) {
+ aPendingRemoteTextures = nullptr;
+ }
+
+ wr::TransactionBuilder fastTxn(mApi, /* aUseSceneBuilderThread */ false);
+ wr::AutoTransactionSender sender(mApi, &fastTxn);
+
+ // Transaction for async image pipeline that uses ImageBridge always need to
+ // be non low priority.
+ auto& sceneBuilderTxn =
+ pipeline->mImageHost->GetAsyncRef() ? aTxnForImageBridge : aTxn;
+
+ // Use transaction of using non scene builder thread when ImageHost uses
+ // ImageBridge. ApplyAsyncImagesOfImageBridge() handles transaction of adding
+ // and updating wr::ImageKeys of ImageHosts that uses ImageBridge. Then
+ // AsyncImagePipelineManager always needs to use non scene builder thread
+ // transaction for adding and updating wr::ImageKeys of ImageHosts that uses
+ // ImageBridge. Otherwise, ordering of wr::ImageKeys updating in webrender
+ // becomes inconsistent.
+ auto& maybeFastTxn = pipeline->mImageHost->GetAsyncRef() ? fastTxn : aTxn;
+
+ wr::Epoch epoch = GetNextImageEpoch();
+ TextureHost* texture =
+ pipeline->mImageHost->GetAsTextureHostForComposite(this);
+ auto* wrapper = texture ? texture->AsRemoteTextureHostWrapper() : nullptr;
+
+ // Store pending remote texture that is used for waiting at WebRenderAPI.
+ if (aPendingRemoteTextures && texture &&
+ texture != pipeline->mCurrentTexture && wrapper) {
+ aPendingRemoteTextures->mList.emplace(wrapper->mTextureId,
+ wrapper->mOwnerId, wrapper->mForPid);
+ }
+
+ if (aPendingOps && !pipeline->mImageHost->GetAsyncRef()) {
+ aPendingOps->mList.emplace(AsyncImagePipelineOp::ApplyAsyncImageForPipeline(
+ this, aPipelineId, texture));
+ return;
+ }
+
+ ApplyAsyncImageForPipeline(epoch, aPipelineId, pipeline, texture,
+ sceneBuilderTxn, maybeFastTxn);
+}
+
+void AsyncImagePipelineManager::ApplyAsyncImageForPipeline(
+ const wr::PipelineId& aPipelineId, TextureHost* aTexture,
+ wr::TransactionBuilder& aTxn) {
+ AsyncImagePipeline* pipeline =
+ mAsyncImagePipelines.Get(wr::AsUint64(aPipelineId));
+ if (!pipeline) {
+ return;
+ }
+ MOZ_ASSERT(!pipeline->mImageHost->GetAsyncRef());
+
+ wr::Epoch epoch = GetNextImageEpoch();
+ ApplyAsyncImageForPipeline(epoch, aPipelineId, pipeline, aTexture, aTxn,
+ aTxn);
+}
+
+void AsyncImagePipelineManager::SetEmptyDisplayList(
+ const wr::PipelineId& aPipelineId, wr::TransactionBuilder& aTxn,
+ wr::TransactionBuilder& aTxnForImageBridge) {
+ AsyncImagePipeline* pipeline =
+ mAsyncImagePipelines.Get(wr::AsUint64(aPipelineId));
+ if (!pipeline) {
+ return;
+ }
+
+ // Transaction for async image pipeline that uses ImageBridge always need to
+ // be non low priority.
+ auto& txn = pipeline->mImageHost->GetAsyncRef() ? aTxnForImageBridge : aTxn;
+
+ wr::Epoch epoch = GetNextImageEpoch();
+ wr::DisplayListBuilder builder(aPipelineId, mApi->GetBackendType());
+ builder.Begin();
+
+ wr::BuiltDisplayList dl;
+ builder.End(dl);
+ txn.SetDisplayList(epoch, aPipelineId, dl.dl_desc, dl.dl_items, dl.dl_cache,
+ dl.dl_spatial_tree);
+}
+
+void AsyncImagePipelineManager::HoldExternalImage(
+ const wr::PipelineId& aPipelineId, const wr::Epoch& aEpoch,
+ TextureHost* aTexture) {
+ if (mDestroyed) {
+ return;
+ }
+ MOZ_ASSERT(aTexture);
+
+ PipelineTexturesHolder* holder =
+ mPipelineTexturesHolders.Get(wr::AsUint64(aPipelineId));
+ MOZ_ASSERT(holder);
+ if (!holder) {
+ return;
+ }
+ if (aTexture->NeedsDeferredDeletion()) {
+ // Hold WebRenderTextureHost until rendering completed.
+ holder->mTextureHostsUntilRenderCompleted.emplace_back(
+ MakeUnique<ForwardingTextureHost>(aEpoch, aTexture));
+ } else {
+ // Hold WebRenderTextureHost until submitted for rendering.
+ holder->mTextureHostsUntilRenderSubmitted.emplace_back(aEpoch, aTexture);
+ }
+}
+
+void AsyncImagePipelineManager::HoldExternalImage(
+ const wr::PipelineId& aPipelineId, const wr::Epoch& aEpoch,
+ const wr::ExternalImageId& aImageId) {
+ if (mDestroyed) {
+ SharedSurfacesParent::Release(aImageId);
+ return;
+ }
+
+ PipelineTexturesHolder* holder =
+ mPipelineTexturesHolders.Get(wr::AsUint64(aPipelineId));
+ MOZ_ASSERT(holder);
+ if (!holder) {
+ SharedSurfacesParent::Release(aImageId);
+ return;
+ }
+
+ holder->mExternalImages.emplace_back(
+ MakeUnique<ForwardingExternalImage>(aEpoch, aImageId));
+}
+
+void AsyncImagePipelineManager::NotifyPipelinesUpdated(
+ RefPtr<const wr::WebRenderPipelineInfo> aInfo,
+ wr::RenderedFrameId aLatestFrameId,
+ wr::RenderedFrameId aLastCompletedFrameId, ipc::FileDescriptor&& aFenceFd) {
+ MOZ_ASSERT(wr::RenderThread::IsInRenderThread());
+ MOZ_ASSERT(mLastCompletedFrameId <= aLastCompletedFrameId.mId);
+ MOZ_ASSERT(aLatestFrameId.IsValid());
+
+ mLastCompletedFrameId = aLastCompletedFrameId.mId;
+
+ {
+ // We need to lock for mRenderSubmittedUpdates because it can be accessed
+ // on the compositor thread.
+ MutexAutoLock lock(mRenderSubmittedUpdatesLock);
+
+ // Move the pending updates into the submitted ones.
+ mRenderSubmittedUpdates.emplace_back(
+ aLatestFrameId,
+ WebRenderPipelineInfoHolder(std::move(aInfo), std::move(aFenceFd)));
+ }
+
+ // Queue a runnable on the compositor thread to process the updates.
+ // This will also call CheckForTextureHostsNotUsedByGPU.
+ layers::CompositorThread()->Dispatch(
+ NewRunnableMethod("ProcessPipelineUpdates", this,
+ &AsyncImagePipelineManager::ProcessPipelineUpdates));
+}
+
+void AsyncImagePipelineManager::ProcessPipelineUpdates() {
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+
+ if (mDestroyed) {
+ return;
+ }
+
+ std::vector<std::pair<wr::RenderedFrameId, WebRenderPipelineInfoHolder>>
+ submittedUpdates;
+ {
+ // We need to lock for mRenderSubmittedUpdates because it can be accessed on
+ // the compositor thread.
+ MutexAutoLock lock(mRenderSubmittedUpdatesLock);
+ mRenderSubmittedUpdates.swap(submittedUpdates);
+ }
+
+ // submittedUpdates is a vector of RenderedFrameIds paired with vectors of
+ // WebRenderPipelineInfo.
+ for (auto update : submittedUpdates) {
+ auto& holder = update.second;
+ const auto& info = holder.mInfo->Raw();
+
+ mReleaseFenceFd = std::move(holder.mFenceFd);
+
+ for (auto& epoch : info.epochs) {
+ ProcessPipelineRendered(epoch.pipeline_id, epoch.epoch, update.first);
+ }
+ for (auto& removedPipeline : info.removed_pipelines) {
+ ProcessPipelineRemoved(removedPipeline, update.first);
+ }
+ }
+ CheckForTextureHostsNotUsedByGPU();
+}
+
+void AsyncImagePipelineManager::ProcessPipelineRendered(
+ const wr::PipelineId& aPipelineId, const wr::Epoch& aEpoch,
+ wr::RenderedFrameId aRenderedFrameId) {
+ if (auto entry = mPipelineTexturesHolders.Lookup(wr::AsUint64(aPipelineId))) {
+ const auto& holder = entry.Data();
+ // For TextureHosts that can be released on render submission, using aEpoch
+ // find the first that we can't release and then release all prior to that.
+ auto firstSubmittedHostToKeep = std::find_if(
+ holder->mTextureHostsUntilRenderSubmitted.begin(),
+ holder->mTextureHostsUntilRenderSubmitted.end(),
+ [&aEpoch](const auto& entry) { return aEpoch <= entry.mEpoch; });
+#ifdef MOZ_WIDGET_ANDROID
+ // Set release fence if TextureHost owns AndroidHardwareBuffer.
+ // The TextureHost handled by mTextureHostsUntilRenderSubmitted instead of
+ // mTextureHostsUntilRenderCompleted, since android fence could be used
+ // to wait until its end of usage by GPU.
+ for (auto it = holder->mTextureHostsUntilRenderSubmitted.begin();
+ it != firstSubmittedHostToKeep; ++it) {
+ const auto& entry = it;
+ if (entry->mTexture->GetAndroidHardwareBuffer() &&
+ mReleaseFenceFd.IsValid()) {
+ ipc::FileDescriptor fenceFd = mReleaseFenceFd;
+ entry->mTexture->SetReleaseFence(std::move(fenceFd));
+ }
+ }
+#endif
+ holder->mTextureHostsUntilRenderSubmitted.erase(
+ holder->mTextureHostsUntilRenderSubmitted.begin(),
+ firstSubmittedHostToKeep);
+
+ // For TextureHosts that need to wait until render completed, find the first
+ // that is later than aEpoch and then move all prior to that to
+ // mTexturesInUseByGPU paired with aRenderedFrameId. These will be released
+ // once rendering has completed for aRenderedFrameId.
+ auto firstCompletedHostToKeep = std::find_if(
+ holder->mTextureHostsUntilRenderCompleted.begin(),
+ holder->mTextureHostsUntilRenderCompleted.end(),
+ [&aEpoch](const auto& entry) { return aEpoch <= entry->mEpoch; });
+ if (firstCompletedHostToKeep !=
+ holder->mTextureHostsUntilRenderCompleted.begin()) {
+ std::vector<UniquePtr<ForwardingTextureHost>> hostsUntilCompleted(
+ std::make_move_iterator(
+ holder->mTextureHostsUntilRenderCompleted.begin()),
+ std::make_move_iterator(firstCompletedHostToKeep));
+ mTexturesInUseByGPU.emplace_back(aRenderedFrameId,
+ std::move(hostsUntilCompleted));
+ holder->mTextureHostsUntilRenderCompleted.erase(
+ holder->mTextureHostsUntilRenderCompleted.begin(),
+ firstCompletedHostToKeep);
+ }
+
+ // Using aEpoch, find the first external image that we can't release and
+ // then release all prior to that.
+ auto firstImageToKeep = std::find_if(
+ holder->mExternalImages.begin(), holder->mExternalImages.end(),
+ [&aEpoch](const auto& entry) { return aEpoch <= entry->mEpoch; });
+ holder->mExternalImages.erase(holder->mExternalImages.begin(),
+ firstImageToKeep);
+ }
+}
+
+void AsyncImagePipelineManager::ProcessPipelineRemoved(
+ const wr::RemovedPipeline& aRemovedPipeline,
+ wr::RenderedFrameId aRenderedFrameId) {
+ if (mDestroyed) {
+ return;
+ }
+ if (auto entry = mPipelineTexturesHolders.Lookup(
+ wr::AsUint64(aRemovedPipeline.pipeline_id))) {
+ const auto& holder = entry.Data();
+ if (holder->mDestroyedEpoch.isSome()) {
+ if (!holder->mTextureHostsUntilRenderCompleted.empty()) {
+ // Move all TextureHosts that must be held until render completed to
+ // mTexturesInUseByGPU paired with aRenderedFrameId.
+ mTexturesInUseByGPU.emplace_back(
+ aRenderedFrameId,
+ std::move(holder->mTextureHostsUntilRenderCompleted));
+ }
+
+ // Remove Pipeline releasing all remaining TextureHosts and external
+ // images.
+ entry.Remove();
+ }
+
+ // If mDestroyedEpoch contains nothing it means we reused the same pipeline
+ // id (probably because we moved the tab to another window). In this case we
+ // need to keep the holder.
+ }
+}
+
+void AsyncImagePipelineManager::CheckForTextureHostsNotUsedByGPU() {
+ uint64_t lastCompletedFrameId = mLastCompletedFrameId;
+
+ // Find first entry after mLastCompletedFrameId and release all prior ones.
+ auto firstTexturesToKeep =
+ std::find_if(mTexturesInUseByGPU.begin(), mTexturesInUseByGPU.end(),
+ [lastCompletedFrameId](const auto& entry) {
+ return lastCompletedFrameId < entry.first.mId;
+ });
+ mTexturesInUseByGPU.erase(mTexturesInUseByGPU.begin(), firstTexturesToKeep);
+}
+
+wr::Epoch AsyncImagePipelineManager::GetNextImageEpoch() {
+ mAsyncImageEpoch.mHandle++;
+ return mAsyncImageEpoch;
+}
+
+AsyncImagePipelineManager::WebRenderPipelineInfoHolder::
+ WebRenderPipelineInfoHolder(RefPtr<const wr::WebRenderPipelineInfo>&& aInfo,
+ ipc::FileDescriptor&& aFenceFd)
+ : mInfo(aInfo), mFenceFd(aFenceFd) {}
+
+AsyncImagePipelineManager::WebRenderPipelineInfoHolder::
+ ~WebRenderPipelineInfoHolder() = default;
+
+} // namespace layers
+} // namespace mozilla
diff --git a/gfx/layers/wr/AsyncImagePipelineManager.h b/gfx/layers/wr/AsyncImagePipelineManager.h
new file mode 100644
index 0000000000..7f8e68ae41
--- /dev/null
+++ b/gfx/layers/wr/AsyncImagePipelineManager.h
@@ -0,0 +1,296 @@
+/* -*- 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/. */
+
+#ifndef MOZILLA_GFX_WEBRENDERCOMPOSITABLE_HOLDER_H
+#define MOZILLA_GFX_WEBRENDERCOMPOSITABLE_HOLDER_H
+
+#include <vector>
+
+#include "CompositableHost.h"
+#include "mozilla/gfx/Point.h"
+#include "mozilla/ipc/FileDescriptor.h"
+#include "mozilla/layers/RemoteTextureMap.h"
+#include "mozilla/layers/TextureHost.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/webrender/WebRenderAPI.h"
+#include "mozilla/webrender/WebRenderTypes.h"
+#include "nsClassHashtable.h"
+
+namespace mozilla {
+
+namespace wr {
+class DisplayListBuilder;
+class WebRenderAPI;
+class WebRenderPipelineInfo;
+} // namespace wr
+
+namespace layers {
+
+class CompositableHost;
+class CompositorVsyncScheduler;
+class WebRenderImageHost;
+class WebRenderTextureHost;
+
+class AsyncImagePipelineManager final {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AsyncImagePipelineManager)
+
+ explicit AsyncImagePipelineManager(RefPtr<wr::WebRenderAPI>&& aApi,
+ bool aUseCompositorWnd);
+
+ protected:
+ ~AsyncImagePipelineManager();
+
+ public:
+ void Destroy();
+
+ bool UseCompositorWnd() const { return mUseCompositorWnd; }
+
+ void AddPipeline(const wr::PipelineId& aPipelineId,
+ WebRenderBridgeParent* aWrBridge);
+ void RemovePipeline(const wr::PipelineId& aPipelineId,
+ const wr::Epoch& aEpoch);
+ WebRenderBridgeParent* GetWrBridge(const wr::PipelineId& aPipelineId);
+
+ void HoldExternalImage(const wr::PipelineId& aPipelineId,
+ const wr::Epoch& aEpoch, TextureHost* aTexture);
+ void HoldExternalImage(const wr::PipelineId& aPipelineId,
+ const wr::Epoch& aEpoch,
+ const wr::ExternalImageId& aImageId);
+
+ // This is called from the Renderer thread to notify this class about the
+ // pipelines in the most recently completed update.
+ // @param aInfo PipelineInfo for the update
+ // @param aLatestFrameId RenderedFrameId if a frame has been submitted for
+ // rendering, invalid if not
+ // @param aLastCompletedFrameId RenderedFrameId for the last completed frame
+ void NotifyPipelinesUpdated(RefPtr<const wr::WebRenderPipelineInfo> aInfo,
+ wr::RenderedFrameId aLatestFrameId,
+ wr::RenderedFrameId aLastCompletedFrameId,
+ ipc::FileDescriptor&& aFenceFd);
+
+ // This is run on the compositor thread to process mRenderSubmittedUpdates. We
+ // make this public because we need to invoke it from other places.
+ void ProcessPipelineUpdates();
+
+ TimeStamp GetCompositionTime() const { return mCompositionTime; }
+ CompositionOpportunityId GetCompositionOpportunityId() const {
+ return mCompositionOpportunityId;
+ }
+
+ void SetCompositionInfo(TimeStamp aTimeStamp,
+ CompositionOpportunityId aCompositionOpportunityId) {
+ mCompositionTime = aTimeStamp;
+ mCompositionOpportunityId = aCompositionOpportunityId;
+ if (!mCompositionTime.IsNull() && !mCompositeUntilTime.IsNull() &&
+ mCompositionTime >= mCompositeUntilTime) {
+ mCompositeUntilTime = TimeStamp();
+ }
+ }
+ void CompositeUntil(TimeStamp aTimeStamp) {
+ if (mCompositeUntilTime.IsNull() || mCompositeUntilTime < aTimeStamp) {
+ mCompositeUntilTime = aTimeStamp;
+ }
+ }
+ TimeStamp GetCompositeUntilTime() const { return mCompositeUntilTime; }
+
+ void AddAsyncImagePipeline(const wr::PipelineId& aPipelineId,
+ WebRenderImageHost* aImageHost);
+ void RemoveAsyncImagePipeline(const wr::PipelineId& aPipelineId,
+ wr::TransactionBuilder& aTxn);
+
+ void UpdateAsyncImagePipeline(const wr::PipelineId& aPipelineId,
+ const LayoutDeviceRect& aScBounds,
+ wr::WrRotation aRotation,
+ const wr::ImageRendering& aFilter,
+ const wr::MixBlendMode& aMixBlendMode);
+ void ApplyAsyncImagesOfImageBridge(wr::TransactionBuilder& aSceneBuilderTxn,
+ wr::TransactionBuilder& aFastTxn);
+ void ApplyAsyncImageForPipeline(
+ const wr::PipelineId& aPipelineId, wr::TransactionBuilder& aTxn,
+ wr::TransactionBuilder& aTxnForImageBridge,
+ AsyncImagePipelineOps* aPendingOps,
+ RemoteTextureInfoList* aPendingRemoteTextures);
+
+ void ApplyAsyncImageForPipeline(const wr::PipelineId& aPipelineId,
+ TextureHost* aTexture,
+ wr::TransactionBuilder& aTxn);
+
+ void SetEmptyDisplayList(const wr::PipelineId& aPipelineId,
+ wr::TransactionBuilder& aTxn,
+ wr::TransactionBuilder& aTxnForImageBridge);
+
+ void AppendImageCompositeNotification(
+ const ImageCompositeNotificationInfo& aNotification) {
+ mImageCompositeNotifications.AppendElement(aNotification);
+ }
+
+ void FlushImageNotifications(
+ nsTArray<ImageCompositeNotificationInfo>* aNotifications) {
+ aNotifications->AppendElements(std::move(mImageCompositeNotifications));
+ }
+
+ void SetWillGenerateFrame();
+ bool GetAndResetWillGenerateFrame();
+
+ static wr::ExternalImageId GetNextExternalImageId();
+
+ void SetTextureFactoryIdentifier(
+ const TextureFactoryIdentifier& aTextureFactoryIdentifier) {
+ mTextureFactoryIdentifier = aTextureFactoryIdentifier;
+ }
+
+ TextureFactoryIdentifier GetTextureFactoryIdentifier() const {
+ return mTextureFactoryIdentifier;
+ }
+
+ private:
+ void ProcessPipelineRendered(const wr::PipelineId& aPipelineId,
+ const wr::Epoch& aEpoch,
+ wr::RenderedFrameId aRenderedFrameId);
+ void ProcessPipelineRemoved(const wr::RemovedPipeline& aRemovedPipeline,
+ wr::RenderedFrameId aRenderedFrameId);
+
+ wr::Epoch GetNextImageEpoch();
+ uint32_t GetNextResourceId() { return ++mResourceId; }
+ wr::IdNamespace GetNamespace() { return mIdNamespace; }
+ wr::ImageKey GenerateImageKey() {
+ wr::ImageKey key;
+ key.mNamespace = GetNamespace();
+ key.mHandle = GetNextResourceId();
+ return key;
+ }
+
+ struct ForwardingTextureHost {
+ ForwardingTextureHost(const wr::Epoch& aEpoch, TextureHost* aTexture)
+ : mEpoch(aEpoch), mTexture(aTexture) {}
+ wr::Epoch mEpoch;
+ CompositableTextureHostRef mTexture;
+ };
+
+ struct ForwardingExternalImage {
+ ForwardingExternalImage(const wr::Epoch& aEpoch,
+ const wr::ExternalImageId& aImageId)
+ : mEpoch(aEpoch), mImageId(aImageId) {}
+ ~ForwardingExternalImage();
+ wr::Epoch mEpoch;
+ wr::ExternalImageId mImageId;
+ };
+
+ struct PipelineTexturesHolder {
+ // Holds forwarding WebRenderTextureHosts.
+ std::vector<ForwardingTextureHost> mTextureHostsUntilRenderSubmitted;
+ // TextureHosts that must be held until rendering has completed. UniquePtr
+ // is used to make the entries movable, ideally ForwardingTextureHost would
+ // be fully movable.
+ std::vector<UniquePtr<ForwardingTextureHost>>
+ mTextureHostsUntilRenderCompleted;
+ std::vector<UniquePtr<ForwardingExternalImage>> mExternalImages;
+ Maybe<wr::Epoch> mDestroyedEpoch;
+ WebRenderBridgeParent* MOZ_NON_OWNING_REF mWrBridge = nullptr;
+ };
+
+ struct AsyncImagePipeline {
+ AsyncImagePipeline(wr::PipelineId aPipelineId,
+ layers::WebRenderBackend aBackend);
+ void Update(const LayoutDeviceRect& aScBounds, wr::WrRotation aRotation,
+ const wr::ImageRendering& aFilter,
+ const wr::MixBlendMode& aMixBlendMode) {
+ mIsChanged |= !mScBounds.IsEqualEdges(aScBounds) ||
+ mRotation != aRotation || mFilter != aFilter ||
+ mMixBlendMode != aMixBlendMode;
+ mScBounds = aScBounds;
+ mRotation = aRotation;
+ mFilter = aFilter;
+ mMixBlendMode = aMixBlendMode;
+ }
+
+ bool mInitialised;
+ bool mIsChanged;
+ bool mUseExternalImage;
+ LayoutDeviceRect mScBounds;
+ wr::WrRotation mRotation;
+ wr::ImageRendering mFilter;
+ wr::MixBlendMode mMixBlendMode;
+ RefPtr<WebRenderImageHost> mImageHost;
+ CompositableTextureHostRef mCurrentTexture;
+ nsTArray<wr::ImageKey> mKeys;
+ wr::DisplayListBuilder mDLBuilder;
+ };
+
+ void ApplyAsyncImageForPipeline(const wr::Epoch& aEpoch,
+ const wr::PipelineId& aPipelineId,
+ AsyncImagePipeline* aPipeline,
+ TextureHost* aTexture,
+ wr::TransactionBuilder& aSceneBuilderTxn,
+ wr::TransactionBuilder& aMaybeFastTxn);
+ Maybe<TextureHost::ResourceUpdateOp> UpdateImageKeys(
+ const wr::Epoch& aEpoch, const wr::PipelineId& aPipelineId,
+ AsyncImagePipeline* aPipeline, TextureHost* aTexture,
+ nsTArray<wr::ImageKey>& aKeys, wr::TransactionBuilder& aSceneBuilderTxn,
+ wr::TransactionBuilder& aMaybeFastTxn);
+ Maybe<TextureHost::ResourceUpdateOp> UpdateWithoutExternalImage(
+ TextureHost* aTexture, wr::ImageKey aKey, TextureHost::ResourceUpdateOp,
+ wr::TransactionBuilder& aTxn);
+
+ void CheckForTextureHostsNotUsedByGPU();
+
+ RefPtr<wr::WebRenderAPI> mApi;
+ bool mUseCompositorWnd;
+ TextureFactoryIdentifier mTextureFactoryIdentifier;
+
+ const wr::IdNamespace mIdNamespace;
+ const bool mUseTripleBuffering;
+ uint32_t mResourceId;
+
+ nsClassHashtable<nsUint64HashKey, PipelineTexturesHolder>
+ mPipelineTexturesHolders;
+ nsClassHashtable<nsUint64HashKey, AsyncImagePipeline> mAsyncImagePipelines;
+ wr::Epoch mAsyncImageEpoch;
+ bool mWillGenerateFrame;
+ bool mDestroyed;
+
+#ifdef XP_WIN
+ bool mUseWebRenderDCompVideoHwOverlayWin;
+ bool mUseWebRenderDCompVideoSwOverlayWin;
+#endif
+
+ // Render time for the current composition.
+ TimeStamp mCompositionTime;
+
+ // CompositionOpportunityId of the current composition.
+ CompositionOpportunityId mCompositionOpportunityId;
+
+ // When nonnull, during rendering, some compositable indicated that it will
+ // change its rendering at this time. In order not to miss it, we composite
+ // on every vsync until this time occurs (this is the latest such time).
+ TimeStamp mCompositeUntilTime;
+
+ nsTArray<ImageCompositeNotificationInfo> mImageCompositeNotifications;
+
+ struct WebRenderPipelineInfoHolder {
+ WebRenderPipelineInfoHolder(RefPtr<const wr::WebRenderPipelineInfo>&& aInfo,
+ ipc::FileDescriptor&& aFenceFd);
+ ~WebRenderPipelineInfoHolder();
+ RefPtr<const wr::WebRenderPipelineInfo> mInfo;
+ ipc::FileDescriptor mFenceFd;
+ };
+
+ std::vector<std::pair<wr::RenderedFrameId, WebRenderPipelineInfoHolder>>
+ mRenderSubmittedUpdates;
+ Mutex mRenderSubmittedUpdatesLock MOZ_UNANNOTATED;
+
+ Atomic<uint64_t> mLastCompletedFrameId;
+ std::vector<std::pair<wr::RenderedFrameId,
+ std::vector<UniquePtr<ForwardingTextureHost>>>>
+ mTexturesInUseByGPU;
+ ipc::FileDescriptor mReleaseFenceFd;
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_WEBRENDERCOMPOSITABLE_HOLDER_H */
diff --git a/gfx/layers/wr/AsyncImagePipelineOp.cpp b/gfx/layers/wr/AsyncImagePipelineOp.cpp
new file mode 100644
index 0000000000..a6642be461
--- /dev/null
+++ b/gfx/layers/wr/AsyncImagePipelineOp.cpp
@@ -0,0 +1,30 @@
+/* -*- 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 "AsyncImagePipelineOp.h"
+
+#include "mozilla/layers/AsyncImagePipelineManager.h"
+
+namespace mozilla {
+namespace layers {
+
+void AsyncImagePipelineOps::HandleOps(wr::TransactionBuilder& aTxn) {
+ MOZ_ASSERT(!mList.empty());
+
+ while (!mList.empty()) {
+ auto& frontOp = mList.front();
+ auto* manager = frontOp.mAsyncImageManager;
+ const auto& pipelineId = frontOp.mPipelineId;
+ const auto& textureHost = frontOp.mTextureHost;
+
+ manager->ApplyAsyncImageForPipeline(pipelineId, textureHost, aTxn);
+
+ mList.pop();
+ }
+}
+
+} // namespace layers
+} // namespace mozilla
diff --git a/gfx/layers/wr/AsyncImagePipelineOp.h b/gfx/layers/wr/AsyncImagePipelineOp.h
new file mode 100644
index 0000000000..140679e794
--- /dev/null
+++ b/gfx/layers/wr/AsyncImagePipelineOp.h
@@ -0,0 +1,73 @@
+/* -*- 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/. */
+
+#ifndef MOZILLA_GFX_AsyncImagePipelineOp_H
+#define MOZILLA_GFX_AsyncImagePipelineOp_H
+
+#include <queue>
+
+#include "mozilla/layers/TextureHost.h"
+#include "mozilla/webrender/webrender_ffi.h"
+#include "Units.h"
+
+namespace mozilla {
+
+namespace wr {
+struct Transaction;
+} // namespace wr
+
+namespace layers {
+
+class AsyncImagePipelineManager;
+class TextureHost;
+
+class AsyncImagePipelineOp {
+ public:
+ enum class Tag {
+ ApplyAsyncImageForPipeline,
+ };
+
+ const Tag mTag;
+
+ AsyncImagePipelineManager* const mAsyncImageManager;
+ const wr::PipelineId mPipelineId;
+ const CompositableTextureHostRef mTextureHost;
+
+ private:
+ AsyncImagePipelineOp(const Tag aTag,
+ AsyncImagePipelineManager* aAsyncImageManager,
+ const wr::PipelineId& aPipelineId,
+ TextureHost* aTextureHost)
+ : mTag(aTag),
+ mAsyncImageManager(aAsyncImageManager),
+ mPipelineId(aPipelineId),
+ mTextureHost(aTextureHost) {
+ MOZ_ASSERT(mTag == Tag::ApplyAsyncImageForPipeline);
+ }
+
+ public:
+ static AsyncImagePipelineOp ApplyAsyncImageForPipeline(
+ AsyncImagePipelineManager* aAsyncImageManager,
+ const wr::PipelineId& aPipelineId, TextureHost* aTextureHost) {
+ return AsyncImagePipelineOp(Tag::ApplyAsyncImageForPipeline,
+ aAsyncImageManager, aPipelineId, aTextureHost);
+ }
+};
+
+struct AsyncImagePipelineOps {
+ explicit AsyncImagePipelineOps(wr::Transaction* aTransaction)
+ : mTransaction(aTransaction) {}
+
+ void HandleOps(wr::TransactionBuilder& aTxn);
+
+ wr::Transaction* const mTransaction;
+ std::queue<AsyncImagePipelineOp> mList;
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif // MOZILLA_GFX_AsyncImagePipelineOp_H
diff --git a/gfx/layers/wr/ClipManager.cpp b/gfx/layers/wr/ClipManager.cpp
new file mode 100644
index 0000000000..930ad8d07d
--- /dev/null
+++ b/gfx/layers/wr/ClipManager.cpp
@@ -0,0 +1,503 @@
+/* -*- 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/ClipManager.h"
+
+#include "DisplayItemClipChain.h"
+#include "FrameMetrics.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/layers/StackingContextHelper.h"
+#include "mozilla/layers/WebRenderLayerManager.h"
+#include "mozilla/webrender/WebRenderAPI.h"
+#include "nsDisplayList.h"
+#include "nsRefreshDriver.h"
+#include "nsStyleStructInlines.h"
+#include "UnitTransforms.h"
+
+static mozilla::LazyLogModule sClipLog("wr.clip");
+#define CLIP_LOG(...) MOZ_LOG(sClipLog, LogLevel::Debug, (__VA_ARGS__))
+
+namespace mozilla {
+namespace layers {
+
+ClipManager::ClipManager() : mManager(nullptr), mBuilder(nullptr) {}
+
+void ClipManager::BeginBuild(WebRenderLayerManager* aManager,
+ wr::DisplayListBuilder& aBuilder) {
+ MOZ_ASSERT(!mManager);
+ mManager = aManager;
+ MOZ_ASSERT(!mBuilder);
+ mBuilder = &aBuilder;
+ MOZ_ASSERT(mCacheStack.empty());
+ mCacheStack.emplace();
+ MOZ_ASSERT(mASROverride.empty());
+ MOZ_ASSERT(mItemClipStack.empty());
+}
+
+void ClipManager::EndBuild() {
+ mBuilder = nullptr;
+ mManager = nullptr;
+ mCacheStack.pop();
+ MOZ_ASSERT(mCacheStack.empty());
+ MOZ_ASSERT(mASROverride.empty());
+ MOZ_ASSERT(mItemClipStack.empty());
+}
+
+void ClipManager::BeginList(const StackingContextHelper& aStackingContext) {
+ CLIP_LOG("begin list %p affects = %d, ref-frame = %d\n", &aStackingContext,
+ aStackingContext.AffectsClipPositioning(),
+ aStackingContext.ReferenceFrameId().isSome());
+
+ ItemClips clips(nullptr, nullptr, 0, false);
+ if (!mItemClipStack.empty()) {
+ clips = mItemClipStack.top();
+ }
+
+ if (aStackingContext.AffectsClipPositioning()) {
+ if (auto referenceFrameId = aStackingContext.ReferenceFrameId()) {
+ PushOverrideForASR(clips.mASR, *referenceFrameId);
+ clips.mScrollId = *referenceFrameId;
+ } else {
+ // Start a new cache
+ mCacheStack.emplace();
+ }
+ if (clips.mChain) {
+ clips.mClipChainId =
+ DefineClipChain(clips.mChain, clips.mAppUnitsPerDevPixel);
+ }
+ }
+
+ CLIP_LOG(" push: clip: %p, asr: %p, scroll =%" PRIuPTR ", clip =%" PRIu64
+ "\n",
+ clips.mChain, clips.mASR, clips.mScrollId.id,
+ clips.mClipChainId.valueOr(wr::WrClipChainId{0}).id);
+
+ mItemClipStack.push(clips);
+}
+
+void ClipManager::EndList(const StackingContextHelper& aStackingContext) {
+ MOZ_ASSERT(!mItemClipStack.empty());
+
+ CLIP_LOG("end list %p\n", &aStackingContext);
+
+ mBuilder->SetClipChainLeaf(Nothing());
+ mItemClipStack.pop();
+
+ if (aStackingContext.AffectsClipPositioning()) {
+ if (aStackingContext.ReferenceFrameId()) {
+ PopOverrideForASR(mItemClipStack.empty() ? nullptr
+ : mItemClipStack.top().mASR);
+ } else {
+ MOZ_ASSERT(!mCacheStack.empty());
+ mCacheStack.pop();
+ }
+ }
+}
+
+void ClipManager::PushOverrideForASR(const ActiveScrolledRoot* aASR,
+ const wr::WrSpatialId& aSpatialId) {
+ wr::WrSpatialId space = GetScrollLayer(aASR);
+
+ CLIP_LOG("Pushing %p override %zu -> %zu\n", aASR, space.id, aSpatialId.id);
+ auto it = mASROverride.insert({space, std::stack<wr::WrSpatialId>()});
+ it.first->second.push(aSpatialId);
+
+ // Start a new cache
+ mCacheStack.emplace();
+
+ // Fix up our cached item clip if needed.
+ if (!mItemClipStack.empty()) {
+ auto& top = mItemClipStack.top();
+ if (top.mASR == aASR) {
+ top.mScrollId = aSpatialId;
+ if (top.mChain) {
+ top.mClipChainId =
+ DefineClipChain(top.mChain, top.mAppUnitsPerDevPixel);
+ }
+ }
+ }
+}
+
+void ClipManager::PopOverrideForASR(const ActiveScrolledRoot* aASR) {
+ MOZ_ASSERT(!mCacheStack.empty());
+ mCacheStack.pop();
+
+ wr::WrSpatialId space = GetScrollLayer(aASR);
+ auto it = mASROverride.find(space);
+ if (it == mASROverride.end()) {
+ MOZ_ASSERT_UNREACHABLE("Push/PopOverrideForASR should be balanced");
+ } else {
+ CLIP_LOG("Popping %p override %zu -> %zu\n", aASR, space.id,
+ it->second.top().id);
+ it->second.pop();
+ }
+
+ if (!mItemClipStack.empty()) {
+ auto& top = mItemClipStack.top();
+ if (top.mASR == aASR) {
+ top.mScrollId = (it == mASROverride.end() || it->second.empty())
+ ? space
+ : it->second.top();
+ if (top.mChain) {
+ top.mClipChainId =
+ DefineClipChain(top.mChain, top.mAppUnitsPerDevPixel);
+ }
+ }
+ }
+
+ if (it != mASROverride.end() && it->second.empty()) {
+ mASROverride.erase(it);
+ }
+}
+
+wr::WrSpatialId ClipManager::SpatialIdAfterOverride(
+ const wr::WrSpatialId& aSpatialId) {
+ auto it = mASROverride.find(aSpatialId);
+ if (it == mASROverride.end()) {
+ return aSpatialId;
+ }
+ MOZ_ASSERT(!it->second.empty());
+ CLIP_LOG("Overriding %zu with %zu\n", aSpatialId.id, it->second.top().id);
+ return it->second.top();
+}
+
+wr::WrSpaceAndClipChain ClipManager::SwitchItem(nsDisplayListBuilder* aBuilder,
+ nsDisplayItem* aItem) {
+ const DisplayItemClipChain* clip = aItem->GetClipChain();
+ const DisplayItemClipChain* inheritedClipChain =
+ mBuilder->GetInheritedClipChain();
+ if (inheritedClipChain && inheritedClipChain != clip) {
+ if (!clip) {
+ clip = mBuilder->GetInheritedClipChain();
+ } else {
+ clip = aBuilder->CreateClipChainIntersection(
+ mBuilder->GetInheritedClipChain(), clip);
+ }
+ }
+ const ActiveScrolledRoot* asr = aItem->GetActiveScrolledRoot();
+ DisplayItemType type = aItem->GetType();
+ if (type == DisplayItemType::TYPE_STICKY_POSITION) {
+ // For sticky position items, the ASR is computed differently depending on
+ // whether the item has a fixed descendant or not. But for WebRender
+ // purposes we always want to use the ASR that would have been used if it
+ // didn't have fixed descendants, which is stored as the "container ASR" on
+ // the sticky item.
+ auto* sticky = static_cast<nsDisplayStickyPosition*>(aItem);
+ asr = sticky->GetContainerASR();
+
+ // If the leafmost clip for the sticky item is just the displayport clip,
+ // then skip it. This allows sticky items to remain visible even if the
+ // rest of the content in the enclosing scrollframe is checkerboarding.
+ if (sticky->IsClippedToDisplayPort() && clip && clip->mASR == asr) {
+ clip = clip->mParent;
+ }
+ }
+
+ CLIP_LOG("processing item %p (%s) asr %p clip %p, inherited = %p\n", aItem,
+ DisplayItemTypeName(aItem->GetType()), asr, clip,
+ inheritedClipChain);
+
+ // In most cases we can combine the leaf of the clip chain with the clip rect
+ // of the display item. This reduces the number of clip items, which avoids
+ // some overhead further down the pipeline.
+ bool separateLeaf = false;
+ if (clip && clip->mASR == asr && clip->mClip.GetRoundedRectCount() == 0) {
+ // Container display items are not currently supported because the clip
+ // rect of a stacking context is not handled the same as normal display
+ // items.
+ separateLeaf = !aItem->GetChildren();
+ }
+
+ // Zoom display items report their bounds etc using the parent document's
+ // APD because zoom items act as a conversion layer between the two different
+ // APDs.
+ const int32_t auPerDevPixel = [&] {
+ if (type == DisplayItemType::TYPE_ZOOM) {
+ return static_cast<nsDisplayZoom*>(aItem)->GetParentAppUnitsPerDevPixel();
+ }
+ return aItem->Frame()->PresContext()->AppUnitsPerDevPixel();
+ }();
+
+ ItemClips clips(asr, clip, auPerDevPixel, separateLeaf);
+ MOZ_ASSERT(!mItemClipStack.empty());
+ if (clips.HasSameInputs(mItemClipStack.top())) {
+ // Early-exit because if the clips are the same as aItem's previous sibling,
+ // then we don't need to do do the work of popping the old stuff and then
+ // pushing it right back on for the new item. Note that if aItem doesn't
+ // have a previous sibling, that means BeginList would have been called
+ // just before this, which will have pushed a ItemClips(nullptr, nullptr)
+ // onto mItemClipStack, so the HasSameInputs check should return false.
+ CLIP_LOG("\tearly-exit for %p\n", aItem);
+ return mItemClipStack.top().GetSpaceAndClipChain();
+ }
+
+ // Pop aItem's previous sibling's stuff from mBuilder in preparation for
+ // pushing aItem's stuff.
+ mItemClipStack.pop();
+
+ // If the leaf of the clip chain is going to be merged with the display item's
+ // clip rect, then we should create a clip chain id from the leaf's parent.
+ if (separateLeaf) {
+ CLIP_LOG("\tseparate leaf detected, ignoring the last clip\n");
+ clip = clip->mParent;
+ }
+
+ // There are two ASR chains here that we need to be fully defined. One is the
+ // ASR chain pointed to by |asr|. The other is the
+ // ASR chain pointed to by clip->mASR. We pick the leafmost
+ // of these two chains because that one will include the other. Calling
+ // DefineScrollLayers with this leafmost ASR will recursively define all the
+ // ASRs that we care about for this item, but will not actually push
+ // anything onto the WR stack.
+ const ActiveScrolledRoot* leafmostASR = asr;
+ if (clip) {
+ leafmostASR = ActiveScrolledRoot::PickDescendant(leafmostASR, clip->mASR);
+ }
+ Maybe<wr::WrSpatialId> leafmostId = DefineScrollLayers(leafmostASR, aItem);
+ Unused << leafmostId;
+
+ // Define all the clips in the item's clip chain, and obtain a clip chain id
+ // for it.
+ clips.mClipChainId = DefineClipChain(clip, auPerDevPixel);
+
+ wr::WrSpatialId space = GetScrollLayer(asr);
+ clips.mScrollId = SpatialIdAfterOverride(space);
+ CLIP_LOG("\tassigning %d -> %d\n", (int)space.id, (int)clips.mScrollId.id);
+
+ // Now that we have the scroll id and a clip id for the item, push it onto
+ // the WR stack.
+ clips.UpdateSeparateLeaf(*mBuilder, auPerDevPixel);
+ auto spaceAndClipChain = clips.GetSpaceAndClipChain();
+
+ CLIP_LOG(" push: clip: %p, asr: %p, scroll = %" PRIuPTR ", clip = %" PRIu64
+ "\n",
+ clips.mChain, clips.mASR, clips.mScrollId.id,
+ clips.mClipChainId.valueOr(wr::WrClipChainId{0}).id);
+
+ mItemClipStack.push(clips);
+
+ CLIP_LOG("done setup for %p\n", aItem);
+ return spaceAndClipChain;
+}
+
+wr::WrSpatialId ClipManager::GetScrollLayer(const ActiveScrolledRoot* aASR) {
+ for (const ActiveScrolledRoot* asr = aASR; asr; asr = asr->mParent) {
+ Maybe<wr::WrSpatialId> space =
+ mBuilder->GetScrollIdForDefinedScrollLayer(asr->GetViewId());
+ if (space) {
+ return *space;
+ }
+
+ // If this ASR doesn't have a scroll ID, then we should check its ancestor.
+ // There may not be one defined because the ASR may not be scrollable or we
+ // failed to get the scroll metadata.
+ }
+
+ Maybe<wr::WrSpatialId> space = mBuilder->GetScrollIdForDefinedScrollLayer(
+ ScrollableLayerGuid::NULL_SCROLL_ID);
+ MOZ_ASSERT(space.isSome());
+ return *space;
+}
+
+Maybe<wr::WrSpatialId> ClipManager::DefineScrollLayers(
+ const ActiveScrolledRoot* aASR, nsDisplayItem* aItem) {
+ if (!aASR) {
+ // Recursion base case
+ return Nothing();
+ }
+ ScrollableLayerGuid::ViewID viewId = aASR->GetViewId();
+ Maybe<wr::WrSpatialId> space =
+ mBuilder->GetScrollIdForDefinedScrollLayer(viewId);
+ if (space) {
+ // If we've already defined this scroll layer before, we can early-exit
+ return space;
+ }
+ // Recurse to define the ancestors
+ Maybe<wr::WrSpatialId> ancestorSpace =
+ DefineScrollLayers(aASR->mParent, aItem);
+
+ Maybe<ScrollMetadata> metadata =
+ aASR->mScrollableFrame->ComputeScrollMetadata(mManager, aItem->Frame(),
+ aItem->ToReferenceFrame());
+ if (!metadata) {
+ MOZ_ASSERT_UNREACHABLE("Expected scroll metadata to be available!");
+ return ancestorSpace;
+ }
+
+ FrameMetrics& metrics = metadata->GetMetrics();
+ if (!metrics.IsScrollable()) {
+ // This item is a scrolling no-op, skip over it in the ASR chain.
+ return ancestorSpace;
+ }
+
+ nsIScrollableFrame* scrollableFrame = aASR->mScrollableFrame;
+ nsIFrame* scrollFrame = do_QueryFrame(scrollableFrame);
+ nsPoint offset = scrollFrame->GetOffsetToCrossDoc(aItem->Frame()) +
+ aItem->ToReferenceFrame();
+ int32_t auPerDevPixel = aItem->Frame()->PresContext()->AppUnitsPerDevPixel();
+ nsRect scrollPort = scrollableFrame->GetScrollPortRect() + offset;
+ LayoutDeviceRect clipBounds =
+ LayoutDeviceRect::FromAppUnits(scrollPort, auPerDevPixel);
+
+ // The content rect that we hand to PushScrollLayer should be relative to
+ // the same origin as the clipBounds that we hand to PushScrollLayer -
+ // that is, both of them should be relative to the stacking context `aSc`.
+ // However, when we get the scrollable rect from the FrameMetrics, the
+ // origin has nothing to do with the position of the frame but instead
+ // represents the minimum allowed scroll offset of the scrollable content.
+ // While APZ uses this to clamp the scroll position, we don't need to send
+ // this to WebRender at all. Instead, we take the position from the
+ // composition bounds.
+ LayoutDeviceRect contentRect =
+ metrics.GetExpandedScrollableRect() * metrics.GetDevPixelsPerCSSPixel();
+ contentRect.MoveTo(clipBounds.TopLeft());
+
+ Maybe<wr::WrSpatialId> parent = ancestorSpace;
+ if (parent) {
+ *parent = SpatialIdAfterOverride(*parent);
+ }
+ // The external scroll offset is accumulated into the local space positions of
+ // display items inside WR, so that the elements hash (intern) to the same
+ // content ID for quick comparisons. To avoid invalidations when the
+ // auPerDevPixel is not a round value, round here directly from app units.
+ // This guarantees we won't introduce any inaccuracy in the external scroll
+ // offset passed to WR.
+ const bool useRoundedOffset =
+ StaticPrefs::apz_rounded_external_scroll_offset();
+ LayoutDevicePoint scrollOffset =
+ useRoundedOffset
+ ? LayoutDevicePoint::FromAppUnitsRounded(
+ scrollableFrame->GetScrollPosition(), auPerDevPixel)
+ : LayoutDevicePoint::FromAppUnits(
+ scrollableFrame->GetScrollPosition(), auPerDevPixel);
+
+ // Currently we track scroll-linked effects at the granularity of documents,
+ // not scroll frames, so we consider a scroll frame to have a scroll-linked
+ // effect whenever its containing document does.
+ nsPresContext* presContext = aItem->Frame()->PresContext();
+ const bool hasScrollLinkedEffect =
+ !StaticPrefs::apz_disable_for_scroll_linked_effects() &&
+ presContext->Document()->HasScrollLinkedEffect();
+
+ return Some(mBuilder->DefineScrollLayer(
+ viewId, parent, wr::ToLayoutRect(contentRect),
+ wr::ToLayoutRect(clipBounds), wr::ToLayoutVector2D(scrollOffset),
+ wr::ToWrAPZScrollGeneration(scrollableFrame->ScrollGenerationOnApz()),
+ wr::ToWrHasScrollLinkedEffect(hasScrollLinkedEffect),
+ wr::SpatialKey(uint64_t(scrollFrame), 0, wr::SpatialKeyKind::Scroll)));
+}
+
+Maybe<wr::WrClipChainId> ClipManager::DefineClipChain(
+ const DisplayItemClipChain* aChain, int32_t aAppUnitsPerDevPixel) {
+ MOZ_ASSERT(!mCacheStack.empty());
+ AutoTArray<wr::WrClipId, 6> allClipIds;
+ ClipIdMap& cache = mCacheStack.top();
+ // Iterate through the clips in the current item's clip chain, define them
+ // in WR, and put their IDs into |clipIds|.
+ for (const DisplayItemClipChain* chain = aChain; chain;
+ chain = chain->mParent) {
+ MOZ_DIAGNOSTIC_ASSERT(chain->mOnStack || !chain->mASR ||
+ chain->mASR->mScrollableFrame);
+
+ if (!chain->mClip.HasClip()) {
+ // This item in the chain is a no-op, skip over it
+ continue;
+ }
+
+ auto emplaceResult = cache.try_emplace(chain);
+ auto& chainClipIds = emplaceResult.first->second;
+ if (!emplaceResult.second) {
+ // Found it in the currently-active cache, so just use the id we have for
+ // it.
+ CLIP_LOG("cache[%p] => hit\n", chain);
+ allClipIds.AppendElements(chainClipIds);
+ continue;
+ }
+
+ LayoutDeviceRect clip = LayoutDeviceRect::FromAppUnits(
+ chain->mClip.GetClipRect(), aAppUnitsPerDevPixel);
+ AutoTArray<wr::ComplexClipRegion, 6> wrRoundedRects;
+ chain->mClip.ToComplexClipRegions(aAppUnitsPerDevPixel, wrRoundedRects);
+
+ wr::WrSpatialId space = GetScrollLayer(chain->mASR);
+ // Define the clip
+ space = SpatialIdAfterOverride(space);
+
+ auto rectClipId =
+ mBuilder->DefineRectClip(Some(space), wr::ToLayoutRect(clip));
+ CLIP_LOG("cache[%p] <= %zu\n", chain, rectClipId.id);
+ chainClipIds.AppendElement(rectClipId);
+
+ for (const auto& complexClip : wrRoundedRects) {
+ auto complexClipId =
+ mBuilder->DefineRoundedRectClip(Some(space), complexClip);
+ CLIP_LOG("cache[%p] <= %zu\n", chain, complexClipId.id);
+ chainClipIds.AppendElement(complexClipId);
+ }
+
+ allClipIds.AppendElements(chainClipIds);
+ }
+
+ if (allClipIds.IsEmpty()) {
+ return Nothing();
+ }
+
+ return Some(mBuilder->DefineClipChain(allClipIds));
+}
+
+ClipManager::~ClipManager() {
+ MOZ_ASSERT(!mBuilder);
+ MOZ_ASSERT(mCacheStack.empty());
+ MOZ_ASSERT(mItemClipStack.empty());
+}
+
+ClipManager::ItemClips::ItemClips(const ActiveScrolledRoot* aASR,
+ const DisplayItemClipChain* aChain,
+ int32_t aAppUnitsPerDevPixel,
+ bool aSeparateLeaf)
+ : mASR(aASR),
+ mChain(aChain),
+ mAppUnitsPerDevPixel(aAppUnitsPerDevPixel),
+ mSeparateLeaf(aSeparateLeaf) {
+ mScrollId = wr::wr_root_scroll_node_id();
+}
+
+void ClipManager::ItemClips::UpdateSeparateLeaf(
+ wr::DisplayListBuilder& aBuilder, int32_t aAppUnitsPerDevPixel) {
+ Maybe<wr::LayoutRect> clipLeaf;
+ if (mSeparateLeaf) {
+ MOZ_ASSERT(mChain);
+ clipLeaf.emplace(wr::ToLayoutRect(LayoutDeviceRect::FromAppUnits(
+ mChain->mClip.GetClipRect(), aAppUnitsPerDevPixel)));
+ }
+
+ aBuilder.SetClipChainLeaf(clipLeaf);
+}
+
+bool ClipManager::ItemClips::HasSameInputs(const ItemClips& aOther) {
+ if (mASR != aOther.mASR || mChain != aOther.mChain ||
+ mSeparateLeaf != aOther.mSeparateLeaf) {
+ return false;
+ }
+ // AUPDP only matters if we have a clip chain, since it's only used to compute
+ // the device space clip rect.
+ if (mChain && mAppUnitsPerDevPixel != aOther.mAppUnitsPerDevPixel) {
+ return false;
+ }
+ return true;
+}
+
+wr::WrSpaceAndClipChain ClipManager::ItemClips::GetSpaceAndClipChain() const {
+ auto spaceAndClipChain = wr::RootScrollNodeWithChain();
+ spaceAndClipChain.space = mScrollId;
+ if (mClipChainId) {
+ spaceAndClipChain.clip_chain = mClipChainId->id;
+ }
+ return spaceAndClipChain;
+}
+
+} // namespace layers
+} // namespace mozilla
diff --git a/gfx/layers/wr/ClipManager.h b/gfx/layers/wr/ClipManager.h
new file mode 100644
index 0000000000..7253fec8bb
--- /dev/null
+++ b/gfx/layers/wr/ClipManager.h
@@ -0,0 +1,150 @@
+/* -*- 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/. */
+
+#ifndef GFX_CLIPMANAGER_H
+#define GFX_CLIPMANAGER_H
+
+#include <stack>
+#include <unordered_map>
+
+#include "mozilla/Attributes.h"
+#include "mozilla/webrender/WebRenderAPI.h"
+
+namespace mozilla {
+
+class nsDisplayItem;
+struct ActiveScrolledRoot;
+struct DisplayItemClipChain;
+
+namespace wr {
+class DisplayListBuilder;
+}
+
+namespace layers {
+
+class StackingContextHelper;
+class WebRenderLayerManager;
+
+/**
+ * This class manages creating and assigning scroll layers and clips in
+ * WebRender based on the gecko display list. It has a few public functions that
+ * are intended to be invoked while traversing the Gecko display list, and it
+ * uses the ASR and clip information from the display list to create the
+ * necessary clip state in WebRender.
+ *
+ * The structure of the clip state in WebRender ends up quite similar to how
+ * it is in Gecko. For each ASR in Gecko, we create a scroll layer (i.e. a
+ * scrolling clip) in WebRender; these form a tree structure similar to the
+ * ASR tree structure. Ancestors of scroll layers are always other scroll
+ * layers, or the root scroll node.
+ * The DisplayItemClipChain list of clips from the gecko display list is
+ * converted to a WR clip chain and pushed on the stack prior to creating
+ * any WR commands for that item, and is popped afterwards. In addition,
+ * the WR clip chain has a parent pointer, which points to the clip chain for
+ * any enclosing stacking context. This again results in a strucuture very
+ * similar to that in Gecko, where the clips from container display items get
+ * applied to the contained display items.
+ */
+class ClipManager {
+ public:
+ ClipManager();
+
+ void BeginBuild(WebRenderLayerManager* aManager,
+ wr::DisplayListBuilder& aBuilder);
+ void EndBuild();
+
+ void BeginList(const StackingContextHelper& aStackingContext);
+ void EndList(const StackingContextHelper& aStackingContext);
+
+ wr::WrSpaceAndClipChain SwitchItem(nsDisplayListBuilder* aBuilder,
+ nsDisplayItem* aItem);
+ ~ClipManager();
+
+ void PushOverrideForASR(const ActiveScrolledRoot* aASR,
+ const wr::WrSpatialId& aSpatialId);
+ void PopOverrideForASR(const ActiveScrolledRoot* aASR);
+
+ private:
+ wr::WrSpatialId SpatialIdAfterOverride(const wr::WrSpatialId& aSpatialId);
+ wr::WrSpatialId GetScrollLayer(const ActiveScrolledRoot* aASR);
+
+ Maybe<wr::WrSpatialId> DefineScrollLayers(const ActiveScrolledRoot* aASR,
+ nsDisplayItem* aItem);
+
+ Maybe<wr::WrClipChainId> DefineClipChain(const DisplayItemClipChain* aChain,
+ int32_t aAppUnitsPerDevPixel);
+
+ WebRenderLayerManager* MOZ_NON_OWNING_REF mManager;
+ wr::DisplayListBuilder* mBuilder;
+
+ // Stack of clip caches. Each cache contains a map from gecko
+ // DisplayItemClipChain objects to webrender WrClipIds, which allows us to
+ // avoid redefining identical clips in WR. However, the gecko
+ // DisplayItemClipChain items get deduplicated quite aggressively, without
+ // regard to things like the enclosing reference frame or mask. On the WR
+ // side, we cannot deduplicate clips that aggressively. So what we do is
+ // any time we enter a new reference frame (for example) we create a new clip
+ // cache on mCacheStack. This ensures we continue caching stuff within a given
+ // reference frame, but disallow caching stuff across reference frames. In
+ // general we need to do this anytime PushOverrideForASR is called, as that is
+ // called for the same set of conditions for which we cannot deduplicate
+ // clips.
+ using ClipIdMap = std::unordered_map<const DisplayItemClipChain*,
+ AutoTArray<wr::WrClipId, 4>>;
+ std::stack<ClipIdMap> mCacheStack;
+
+ // A map that holds the cache overrides created by (a) "out of band" clips,
+ // i.e. clips that are generated by display items but that ClipManager
+ // doesn't know about and (b) stacking contexts that affect clip positioning.
+ // These are called "cache overrides" because while we're inside these things,
+ // we cannot use the ASR from the gecko display list as-is. Fundamentally this
+ // results from a mismatch between the ASR+clip items on the gecko side and
+ // the ClipScrollTree on the WR side; the WR side incorporates things like
+ // transforms and stacking context origins while the gecko side manages those
+ // differently.
+ // Any time ClipManager wants to define a new clip as a child of ASR X, it
+ // should first check the cache overrides to see if there is a cache override
+ // item ((a) or (b) above) that is already a child of X, and then define that
+ // clip as a child of Y instead. This map stores X -> Y, which allows
+ // ClipManager to do the necessary lookup. Note that there theoretically might
+ // be multiple different "Y" clips (in case of nested cache overrides), which
+ // is why we need a stack.
+ std::unordered_map<wr::WrSpatialId, std::stack<wr::WrSpatialId>> mASROverride;
+
+ // This holds some clip state for a single nsDisplayItem
+ struct ItemClips {
+ ItemClips(const ActiveScrolledRoot* aASR,
+ const DisplayItemClipChain* aChain, int32_t aAppUnitsPerDevPixel,
+ bool aSeparateLeaf);
+
+ // These are the "inputs" - they come from the nsDisplayItem
+ const ActiveScrolledRoot* mASR;
+ const DisplayItemClipChain* mChain;
+ int32_t mAppUnitsPerDevPixel;
+ bool mSeparateLeaf;
+
+ // These are the "outputs" - they are pushed to WR as needed
+ wr::WrSpatialId mScrollId;
+ Maybe<wr::WrClipChainId> mClipChainId;
+
+ void UpdateSeparateLeaf(wr::DisplayListBuilder& aBuilder,
+ int32_t aAppUnitsPerDevPixel);
+ bool HasSameInputs(const ItemClips& aOther);
+ wr::WrSpaceAndClipChain GetSpaceAndClipChain() const;
+ };
+
+ // A stack of ItemClips corresponding to the nsDisplayItem ancestry. Each
+ // time we recurse into a nsDisplayItem's child list, this stack size
+ // increases by one. The topmost item on the stack is for the display item
+ // we are currently processing and items deeper on the stack are for that
+ // display item's ancestors.
+ std::stack<ItemClips> mItemClipStack;
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif
diff --git a/gfx/layers/wr/DisplayItemCache.cpp b/gfx/layers/wr/DisplayItemCache.cpp
new file mode 100644
index 0000000000..cd55da7424
--- /dev/null
+++ b/gfx/layers/wr/DisplayItemCache.cpp
@@ -0,0 +1,203 @@
+/* -*- 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 "DisplayItemCache.h"
+#include "nsDisplayList.h"
+
+namespace mozilla {
+namespace layers {
+
+DisplayItemCache::DisplayItemCache()
+ : mDisplayList(nullptr),
+ mMaximumSize(0),
+ mPipelineId{},
+ mCaching(false),
+ mInvalid(false),
+ mSuppressed(false) {}
+
+void DisplayItemCache::SetDisplayList(nsDisplayListBuilder* aBuilder,
+ nsDisplayList* aList) {
+ if (!IsEnabled()) {
+ return;
+ }
+
+ MOZ_ASSERT(aBuilder);
+ MOZ_ASSERT(aList);
+
+ const bool listChanged = mDisplayList != aList;
+ const bool partialBuild = !aBuilder->PartialBuildFailed();
+
+ if (listChanged && partialBuild) {
+ // If the display list changed during a partial update, it means that
+ // |SetDisplayList()| has missed one rebuilt display list.
+ mDisplayList = nullptr;
+ return;
+ }
+
+ if (listChanged || !partialBuild) {
+ // The display list has been changed or rebuilt.
+ mDisplayList = aList;
+ mInvalid = true;
+ }
+
+ UpdateState();
+}
+
+void DisplayItemCache::SetPipelineId(const wr::PipelineId& aPipelineId) {
+ mInvalid = mInvalid || !(mPipelineId == aPipelineId);
+ mPipelineId = aPipelineId;
+}
+
+void DisplayItemCache::UpdateState() {
+ // |mCaching == true| if:
+ // 1) |SetDisplayList()| is called with a fully rebuilt display list
+ // followed by
+ // 2a) |SetDisplayList()| is called with a partially updated display list
+ // or
+ // 2b) |SkipWaitingForPartialDisplayList()| is called
+ mCaching = !mInvalid;
+
+#if 0
+ Stats().Print();
+ Stats().Reset();
+#endif
+
+ if (IsEmpty()) {
+ // The cache is empty so nothing needs to be updated.
+ mInvalid = false;
+ return;
+ }
+
+ // Clear the cache if the current state is invalid.
+ if (mInvalid) {
+ Clear();
+ } else {
+ FreeUnusedSlots();
+ }
+
+ mInvalid = false;
+}
+
+void DisplayItemCache::Clear() {
+ memset(mSlots.Elements(), 0, mSlots.Length() * sizeof(Slot));
+ mFreeSlots.ClearAndRetainStorage();
+
+ for (size_t i = 0; i < CurrentSize(); ++i) {
+ mFreeSlots.AppendElement(i);
+ }
+}
+
+Maybe<uint16_t> DisplayItemCache::GetNextFreeSlot() {
+ if (mFreeSlots.IsEmpty() && !GrowIfPossible()) {
+ return Nothing();
+ }
+
+ return Some(mFreeSlots.PopLastElement());
+}
+
+bool DisplayItemCache::GrowIfPossible() {
+ if (IsFull()) {
+ return false;
+ }
+
+ const auto currentSize = CurrentSize();
+ MOZ_ASSERT(currentSize <= mMaximumSize);
+
+ // New slots are added one by one, which is amortized O(1) time complexity due
+ // to underlying storage implementation.
+ mSlots.AppendElement();
+ mFreeSlots.AppendElement(currentSize);
+ return true;
+}
+
+void DisplayItemCache::FreeUnusedSlots() {
+ for (size_t i = 0; i < CurrentSize(); ++i) {
+ auto& slot = mSlots[i];
+
+ if (!slot.mUsed && slot.mOccupied) {
+ // This entry contained a cached item, but was not used.
+ slot.mOccupied = false;
+ mFreeSlots.AppendElement(i);
+ }
+
+ slot.mUsed = false;
+ }
+}
+
+void DisplayItemCache::SetCapacity(const size_t aInitialSize,
+ const size_t aMaximumSize) {
+ mMaximumSize = aMaximumSize;
+ mSlots.SetLength(aInitialSize);
+ mFreeSlots.SetCapacity(aMaximumSize);
+ Clear();
+}
+
+Maybe<uint16_t> DisplayItemCache::AssignSlot(nsPaintedDisplayItem* aItem) {
+ if (!mCaching || !aItem->CanBeReused() || !aItem->CanBeCached()) {
+ return Nothing();
+ }
+
+ auto& slot = aItem->CacheIndex();
+
+ if (!slot) {
+ slot = GetNextFreeSlot();
+ if (!slot) {
+ // The item does not fit in the cache.
+ return Nothing();
+ }
+ }
+
+ MOZ_ASSERT(slot && CurrentSize() > *slot);
+ return slot;
+}
+
+void DisplayItemCache::MarkSlotOccupied(
+ uint16_t aSlotIndex, const wr::WrSpaceAndClipChain& aSpaceAndClip) {
+ // Caching of the item succeeded, update the slot state.
+ auto& slot = mSlots[aSlotIndex];
+ MOZ_ASSERT(!slot.mOccupied);
+ slot.mOccupied = true;
+ MOZ_ASSERT(!slot.mUsed);
+ slot.mUsed = true;
+ slot.mSpaceAndClip = aSpaceAndClip;
+}
+
+Maybe<uint16_t> DisplayItemCache::CanReuseItem(
+ nsPaintedDisplayItem* aItem, const wr::WrSpaceAndClipChain& aSpaceAndClip) {
+ auto& slotIndex = aItem->CacheIndex();
+ if (!slotIndex) {
+ return Nothing();
+ }
+
+ MOZ_ASSERT(slotIndex && CurrentSize() > *slotIndex);
+
+ auto& slot = mSlots[*slotIndex];
+ if (!slot.mOccupied) {
+ // The display item has a stale cache slot. Recache the item.
+ return Nothing();
+ }
+
+ if (mSuppressed) {
+ slot.mOccupied = false;
+ slotIndex = Nothing();
+ return Nothing();
+ }
+
+ if (!(aSpaceAndClip == slot.mSpaceAndClip)) {
+ // Spatial id and clip id can change between display lists, if items that
+ // generate them change their order.
+ slot.mOccupied = false;
+ aItem->SetCantBeCached();
+ slotIndex = Nothing();
+ } else {
+ slot.mUsed = true;
+ }
+
+ return slotIndex;
+}
+
+} // namespace layers
+} // namespace mozilla
diff --git a/gfx/layers/wr/DisplayItemCache.h b/gfx/layers/wr/DisplayItemCache.h
new file mode 100644
index 0000000000..a44b4a52f5
--- /dev/null
+++ b/gfx/layers/wr/DisplayItemCache.h
@@ -0,0 +1,210 @@
+/* -*- 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/. */
+
+#ifndef GFX_DISPLAY_ITEM_CACHE_H
+#define GFX_DISPLAY_ITEM_CACHE_H
+
+#include "mozilla/webrender/WebRenderAPI.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+
+class nsDisplayList;
+class nsDisplayListBuilder;
+class nsPaintedDisplayItem;
+
+namespace wr {
+class DisplayListBuilder;
+} // namespace wr
+
+namespace layers {
+
+class CacheStats {
+ public:
+ CacheStats() = default;
+
+ void Reset() { mCached = mReused = mTotal = 0; }
+
+ void Print() {
+ static uint64_t avgC = 1;
+ static uint64_t avgR = 1;
+ static uint64_t avgT = 1;
+
+ avgC += mCached;
+ avgR += mReused;
+ avgT += mTotal;
+
+ printf("Cached: %zu (avg: %f), Reused: %zu (avg: %f), Total: %zu\n",
+ mCached, (double)avgC / (double)avgT, mReused,
+ (double)avgR / (double)avgT, mTotal);
+ }
+
+ void AddCached() { mCached++; }
+ void AddReused() { mReused++; }
+ void AddTotal() { mTotal++; }
+
+ private:
+ size_t mCached = 0;
+ size_t mReused = 0;
+ size_t mTotal = 0;
+};
+
+/**
+ * DisplayItemCache keeps track of which Gecko display items have already had
+ * their respective WebRender display items sent to WebRender backend.
+ *
+ * Ideally creating the WR display items for a Gecko display item would not
+ * depend on any external state. However currently pipeline id, clip id, and
+ * spatial id can change between display lists, even if the Gecko display items
+ * have not. This state is tracked by DisplayItemCache.
+ */
+class DisplayItemCache final {
+ public:
+ DisplayItemCache();
+
+ /**
+ * Clears the cache.
+ */
+ void Clear();
+
+ /**
+ * Sets the initial and max cache size to given |aInitialSize| and |aMaxSize|.
+ */
+ void SetCapacity(const size_t aInitialSize, const size_t aMaximumSize);
+
+ /**
+ * Sets the display list used by the cache.
+ */
+ void SetDisplayList(nsDisplayListBuilder* aBuilder, nsDisplayList* aList);
+
+ /**
+ * Sets the pipeline id used by the cache.
+ */
+ void SetPipelineId(const wr::PipelineId& aPipelineId);
+
+ /**
+ * Enables caching immediately if the cache is valid, and display list is set.
+ */
+ void SkipWaitingForPartialDisplayList() {
+ mCaching = mDisplayList && !mInvalid;
+ }
+
+ /**
+ * Returns true if display item caching is enabled, otherwise false.
+ */
+ bool IsEnabled() const { return !mSuppressed && mMaximumSize > 0; }
+
+ /**
+ * Suppress display item caching. This doesn't clear any existing cached
+ * items or change the underlying capacity, it just makes IsEnabled() return
+ * false.
+ * It will also make CanReuseItem return false for the duration of the
+ * suppression.
+ */
+ bool SetSuppressed(bool aSuppressed) {
+ if (aSuppressed == mSuppressed) {
+ return mSuppressed;
+ }
+ mSuppressed = aSuppressed;
+ return !mSuppressed;
+ }
+
+ /**
+ * Returns true if there are no cached items, otherwise false.
+ */
+ bool IsEmpty() const { return mFreeSlots.Length() == CurrentSize(); }
+
+ /**
+ * Returns true if the cache has reached the maximum size, otherwise false.
+ */
+ bool IsFull() const {
+ return mFreeSlots.IsEmpty() && CurrentSize() == mMaximumSize;
+ }
+
+ /**
+ * Returns the current cache size.
+ */
+ size_t CurrentSize() const { return mSlots.Length(); }
+
+ /**
+ * If there are free slots in the cache, assigns a cache slot to the given
+ * display item |aItem| and returns it. Otherwise returns Nothing().
+ */
+ Maybe<uint16_t> AssignSlot(nsPaintedDisplayItem* aItem);
+
+ /**
+ * Marks the slot with the given |slotIndex| occupied and used.
+ * Also stores the current space and clipchain |aSpaceAndClip|.
+ */
+ void MarkSlotOccupied(uint16_t slotIndex,
+ const wr::WrSpaceAndClipChain& aSpaceAndClip);
+
+ /**
+ * Returns the slot index of the the given display item |aItem|, if the item
+ * can be reused. The current space and clipchain |aSpaceAndClip| is used to
+ * check whether the cached item is still valid.
+ * If the item cannot be reused, returns Nothing().
+ */
+ Maybe<uint16_t> CanReuseItem(nsPaintedDisplayItem* aItem,
+ const wr::WrSpaceAndClipChain& aSpaceAndClip);
+
+ CacheStats& Stats() { return mCacheStats; }
+
+ private:
+ struct Slot {
+ Slot() : mSpaceAndClip{}, mOccupied(false), mUsed(false) {}
+
+ wr::WrSpaceAndClipChain mSpaceAndClip;
+ bool mOccupied;
+ bool mUsed;
+ };
+
+ void FreeUnusedSlots();
+ Maybe<uint16_t> GetNextFreeSlot();
+ bool GrowIfPossible();
+ void UpdateState();
+
+ // The lifetime of display lists exceed the lifetime of DisplayItemCache.
+ // This pointer stores the address of the display list that is using this
+ // cache, and it is only used for pointer comparisons.
+ nsDisplayList* mDisplayList;
+
+ size_t mMaximumSize;
+ nsTArray<Slot> mSlots;
+ nsTArray<uint16_t> mFreeSlots;
+
+ wr::PipelineId mPipelineId;
+ bool mCaching;
+ bool mInvalid;
+ bool mSuppressed;
+
+ CacheStats mCacheStats;
+};
+
+class MOZ_RAII AutoDisplayItemCacheSuppressor {
+ public:
+ explicit AutoDisplayItemCacheSuppressor(DisplayItemCache* aCache)
+ : mCache(aCache) {
+ mWasSuppressed = mCache->SetSuppressed(true);
+ }
+
+ // Note that this restores the original state rather than unconditionally
+ // unsuppressing the cache for future-proofing/robustification. Currently
+ // we only ever use this RAII in one non-recursive function, but we might
+ // decide to expand its usage to other scenarios and end up with nested
+ // suppressions, in which case restoring the state back to what we found it
+ // is better.
+ ~AutoDisplayItemCacheSuppressor() { mCache->SetSuppressed(mWasSuppressed); }
+
+ private:
+ DisplayItemCache* mCache;
+ bool mWasSuppressed;
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif /* GFX_DISPLAY_ITEM_CACHE_H */
diff --git a/gfx/layers/wr/HitTestInfoManager.cpp b/gfx/layers/wr/HitTestInfoManager.cpp
new file mode 100644
index 0000000000..552221b14c
--- /dev/null
+++ b/gfx/layers/wr/HitTestInfoManager.cpp
@@ -0,0 +1,134 @@
+/* -*- 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 "HitTestInfoManager.h"
+#include "HitTestInfo.h"
+
+#include "nsDisplayList.h"
+
+#define DEBUG_HITTEST_INFO 0
+#if DEBUG_HITTEST_INFO
+# define HITTEST_INFO_LOG(...) printf_stderr(__VA_ARGS__)
+#else
+# define HITTEST_INFO_LOG(...)
+#endif
+
+namespace mozilla::layers {
+
+using ViewID = ScrollableLayerGuid::ViewID;
+
+/**
+ * TODO(miko): This used to be a performance bottle-neck, but it does not show
+ * up in profiles anymore, see bugs 1424637 and 1424968.
+ * A better way of doing this would be to store current app units per dev pixel
+ * in wr::DisplayListBuilder, and update it whenever display items that separate
+ * presshell boundaries are encountered.
+ */
+static int32_t GetAppUnitsFromDisplayItem(nsDisplayItem* aItem) {
+ nsIFrame* frame = aItem->Frame();
+ MOZ_ASSERT(frame);
+ return frame->PresContext()->AppUnitsPerDevPixel();
+}
+
+static void CreateWebRenderCommands(wr::DisplayListBuilder& aBuilder,
+ nsDisplayItem* aItem, const nsRect& aArea,
+ const gfx::CompositorHitTestInfo& aFlags,
+ const ViewID& aViewId) {
+ const Maybe<SideBits> sideBits =
+ aBuilder.GetContainingFixedPosSideBits(aItem->GetActiveScrolledRoot());
+
+ const LayoutDeviceRect devRect =
+ LayoutDeviceRect::FromAppUnits(aArea, GetAppUnitsFromDisplayItem(aItem));
+ const wr::LayoutRect rect = wr::ToLayoutRect(devRect);
+
+ aBuilder.PushHitTest(rect, rect, !aItem->BackfaceIsHidden(), aViewId, aFlags,
+ sideBits.valueOr(SideBits::eNone));
+}
+
+HitTestInfoManager::HitTestInfoManager()
+ : mFlags(gfx::CompositorHitTestInvisibleToHit),
+ mViewId(ScrollableLayerGuid::NULL_SCROLL_ID),
+ mSpaceAndClipChain(wr::InvalidScrollNodeWithChain()) {}
+
+void HitTestInfoManager::Reset() {
+ mArea = nsRect();
+ mFlags = gfx::CompositorHitTestInvisibleToHit;
+ mViewId = ScrollableLayerGuid::NULL_SCROLL_ID;
+ mSpaceAndClipChain = wr::InvalidScrollNodeWithChain();
+
+ HITTEST_INFO_LOG("* HitTestInfoManager::Reset\n");
+}
+
+bool HitTestInfoManager::ProcessItem(
+ nsDisplayItem* aItem, wr::DisplayListBuilder& aBuilder,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ MOZ_ASSERT(aItem);
+
+ HITTEST_INFO_LOG("* HitTestInfoManager::ProcessItem(%d, %s, has=%d)\n",
+ getpid(), aItem->Frame()->ListTag().get(),
+ aItem->HasHitTestInfo());
+
+ if (MOZ_UNLIKELY(aItem->GetType() == DisplayItemType::TYPE_REMOTE)) {
+ // Remote frames might contain hit-test-info items inside (but those
+ // aren't processed by this process of course), so we can't optimize out the
+ // next hit-test info item because it might be on top of the iframe.
+ Reset();
+ }
+
+ if (!aItem->HasHitTestInfo()) {
+ return false;
+ }
+
+ const HitTestInfo& hitTestInfo = aItem->GetHitTestInfo();
+ const nsRect& area = hitTestInfo.Area();
+ const gfx::CompositorHitTestInfo& flags = hitTestInfo.Info();
+
+ if (flags == gfx::CompositorHitTestInvisibleToHit || area.IsEmpty()) {
+ return false;
+ }
+
+ const auto viewId =
+ hitTestInfo.GetViewId(aBuilder, aItem->GetActiveScrolledRoot());
+ const auto spaceAndClipChain = aBuilder.CurrentSpaceAndClipChain();
+
+ if (!Update(area, flags, viewId, spaceAndClipChain)) {
+ // The previous hit test information is still valid.
+ return false;
+ }
+
+ HITTEST_INFO_LOG("+ [%d, %d, %d, %d]: flags: 0x%x, viewId: %lu\n", area.x,
+ area.y, area.width, area.height, flags.serialize(), viewId);
+
+ CreateWebRenderCommands(aBuilder, aItem, area, flags, viewId);
+
+ return true;
+}
+
+/**
+ * Updates the current hit testing information if necessary.
+ * Returns true if the hit testing information was changed.
+ */
+bool HitTestInfoManager::Update(const nsRect& aArea,
+ const gfx::CompositorHitTestInfo& aFlags,
+ const ViewID& aViewId,
+ const wr::WrSpaceAndClipChain& aSpaceAndClip) {
+ if (mViewId == aViewId && mFlags == aFlags && mArea.Contains(aArea) &&
+ mSpaceAndClipChain == aSpaceAndClip) {
+ // The previous hit testing information can be reused.
+ HITTEST_INFO_LOG("s [%d, %d, %d, %d]: flags: 0x%x, viewId: %lu\n", aArea.x,
+ aArea.y, aArea.width, aArea.height, aFlags.serialize(),
+ aViewId);
+ return false;
+ }
+
+ mArea = aArea;
+ mFlags = aFlags;
+ mViewId = aViewId;
+ mSpaceAndClipChain = aSpaceAndClip;
+ return true;
+}
+
+} // namespace mozilla::layers
diff --git a/gfx/layers/wr/HitTestInfoManager.h b/gfx/layers/wr/HitTestInfoManager.h
new file mode 100644
index 0000000000..5ee7b566dc
--- /dev/null
+++ b/gfx/layers/wr/HitTestInfoManager.h
@@ -0,0 +1,66 @@
+/* -*- 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/. */
+
+#ifndef GFX_HITTESTINFOMANAGER_H
+#define GFX_HITTESTINFOMANAGER_H
+
+#include "mozilla/gfx/CompositorHitTestInfo.h"
+#include "mozilla/layers/ScrollableLayerGuid.h"
+#include "mozilla/webrender/WebRenderAPI.h"
+#include "nsRect.h"
+
+namespace mozilla {
+
+class nsDisplayItem;
+class nsDisplayListBuilder;
+
+namespace wr {
+class DisplayListBuilder;
+}
+
+namespace layers {
+
+/**
+ * This class extracts the hit testing information (area, flags, ViewId) from
+ * Gecko display items and pushes them into WebRender display list.
+ *
+ * The hit testing information is deduplicated: a new hit test item is only
+ * added if the new area is not contained in the previous area, or if the flags,
+ * ViewId, or current spatial id is different.
+ */
+class HitTestInfoManager {
+ public:
+ HitTestInfoManager();
+
+ /**
+ * Resets the previous hit testing information.
+ */
+ void Reset();
+
+ /**
+ * Extracts the hit testing information from |aItem|, and if necessary, adds
+ * a new WebRender hit test item using |aBuilder|.
+ *
+ * Returns true if a hit test item was pushed.
+ */
+ bool ProcessItem(nsDisplayItem* aItem, wr::DisplayListBuilder& aBuilder,
+ nsDisplayListBuilder* aDisplayListBuilder);
+
+ private:
+ bool Update(const nsRect& aArea, const gfx::CompositorHitTestInfo& aFlags,
+ const ScrollableLayerGuid::ViewID& aViewId,
+ const wr::WrSpaceAndClipChain& aSpaceAndClip);
+
+ nsRect mArea;
+ gfx::CompositorHitTestInfo mFlags;
+ ScrollableLayerGuid::ViewID mViewId;
+ wr::WrSpaceAndClipChain mSpaceAndClipChain;
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif
diff --git a/gfx/layers/wr/IpcResourceUpdateQueue.cpp b/gfx/layers/wr/IpcResourceUpdateQueue.cpp
new file mode 100644
index 0000000000..d19dc7f2f9
--- /dev/null
+++ b/gfx/layers/wr/IpcResourceUpdateQueue.cpp
@@ -0,0 +1,484 @@
+/* -*- 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 "IpcResourceUpdateQueue.h"
+#include <string.h>
+#include <algorithm>
+#include "mozilla/Maybe.h"
+#include "mozilla/ipc/SharedMemory.h"
+#include "mozilla/layers/PTextureChild.h"
+#include "mozilla/layers/WebRenderBridgeChild.h"
+
+namespace mozilla {
+namespace wr {
+
+using namespace mozilla::layers;
+
+ShmSegmentsWriter::ShmSegmentsWriter(layers::WebRenderBridgeChild* aAllocator,
+ size_t aChunkSize)
+ : mShmAllocator(aAllocator), mCursor(0), mChunkSize(aChunkSize) {
+ MOZ_ASSERT(mShmAllocator);
+}
+
+ShmSegmentsWriter::~ShmSegmentsWriter() { Clear(); }
+
+ShmSegmentsWriter::ShmSegmentsWriter(ShmSegmentsWriter&& aOther) noexcept
+ : mSmallAllocs(std::move(aOther.mSmallAllocs)),
+ mLargeAllocs(std::move(aOther.mLargeAllocs)),
+ mShmAllocator(aOther.mShmAllocator),
+ mCursor(aOther.mCursor),
+ mChunkSize(aOther.mChunkSize) {
+ aOther.mCursor = 0;
+}
+
+ShmSegmentsWriter& ShmSegmentsWriter::operator=(
+ ShmSegmentsWriter&& aOther) noexcept {
+ MOZ_ASSERT(IsEmpty(), "Will forget existing updates!");
+ Clear();
+ mSmallAllocs = std::move(aOther.mSmallAllocs);
+ mLargeAllocs = std::move(aOther.mLargeAllocs);
+ mShmAllocator = aOther.mShmAllocator;
+ mCursor = aOther.mCursor;
+ mChunkSize = aOther.mChunkSize;
+ aOther.mCursor = 0;
+ return *this;
+}
+
+layers::OffsetRange ShmSegmentsWriter::Write(Range<uint8_t> aBytes) {
+ const size_t start = mCursor;
+ const size_t length = aBytes.length();
+
+ if (length >= mChunkSize * 4) {
+ auto range = AllocLargeChunk(length);
+ if (range.length()) {
+ // Allocation was successful
+ uint8_t* dstPtr = mLargeAllocs.LastElement().get<uint8_t>();
+ memcpy(dstPtr, aBytes.begin().get(), length);
+ }
+ return range;
+ }
+
+ int remainingBytesToCopy = length;
+
+ size_t srcCursor = 0;
+ size_t dstCursor = mCursor;
+ size_t currAllocLen = mSmallAllocs.Length();
+
+ while (remainingBytesToCopy > 0) {
+ if (dstCursor >= mSmallAllocs.Length() * mChunkSize) {
+ if (!AllocChunk()) {
+ // Allocation failed, so roll back to the state at the start of this
+ // Write() call and abort.
+ while (mSmallAllocs.Length() > currAllocLen) {
+ RefCountedShmem shm = mSmallAllocs.PopLastElement();
+ RefCountedShm::Dealloc(mShmAllocator, shm);
+ }
+ MOZ_ASSERT(mSmallAllocs.Length() == currAllocLen);
+ return layers::OffsetRange(0, start, 0);
+ }
+ // Allocation succeeded, so dstCursor should now be pointing to
+ // something inside the allocation buffer
+ MOZ_ASSERT(dstCursor < (mSmallAllocs.Length() * mChunkSize));
+ }
+
+ const size_t dstMaxOffset = mChunkSize * mSmallAllocs.Length();
+ const size_t dstBaseOffset = mChunkSize * (mSmallAllocs.Length() - 1);
+
+ MOZ_ASSERT(dstCursor >= dstBaseOffset);
+ MOZ_ASSERT(dstCursor <= dstMaxOffset);
+
+ size_t availableRange = dstMaxOffset - dstCursor;
+ size_t copyRange = std::min<int>(availableRange, remainingBytesToCopy);
+
+ uint8_t* srcPtr = &aBytes[srcCursor];
+ uint8_t* dstPtr = RefCountedShm::GetBytes(mSmallAllocs.LastElement()) +
+ (dstCursor - dstBaseOffset);
+
+ memcpy(dstPtr, srcPtr, copyRange);
+
+ srcCursor += copyRange;
+ dstCursor += copyRange;
+ remainingBytesToCopy -= copyRange;
+
+ // sanity check
+ MOZ_ASSERT(remainingBytesToCopy >= 0);
+ }
+
+ mCursor += length;
+
+ return layers::OffsetRange(0, start, length);
+}
+
+bool ShmSegmentsWriter::AllocChunk() {
+ RefCountedShmem shm;
+ if (!mShmAllocator->AllocResourceShmem(mChunkSize, shm)) {
+ gfxCriticalNote << "ShmSegmentsWriter failed to allocate chunk #"
+ << mSmallAllocs.Length();
+ MOZ_ASSERT(false, "ShmSegmentsWriter fails to allocate chunk");
+ return false;
+ }
+ RefCountedShm::AddRef(shm);
+ mSmallAllocs.AppendElement(shm);
+ return true;
+}
+
+layers::OffsetRange ShmSegmentsWriter::AllocLargeChunk(size_t aSize) {
+ ipc::Shmem shm;
+ if (!mShmAllocator->AllocShmem(aSize, &shm)) {
+ gfxCriticalNote
+ << "ShmSegmentsWriter failed to allocate large chunk of size " << aSize;
+ MOZ_ASSERT(false, "ShmSegmentsWriter fails to allocate large chunk");
+ return layers::OffsetRange(0, 0, 0);
+ }
+ mLargeAllocs.AppendElement(shm);
+
+ return layers::OffsetRange(mLargeAllocs.Length(), 0, aSize);
+}
+
+void ShmSegmentsWriter::Flush(nsTArray<RefCountedShmem>& aSmallAllocs,
+ nsTArray<ipc::Shmem>& aLargeAllocs) {
+ MOZ_ASSERT(aSmallAllocs.IsEmpty());
+ MOZ_ASSERT(aLargeAllocs.IsEmpty());
+ aSmallAllocs = std::move(mSmallAllocs);
+ aLargeAllocs = std::move(mLargeAllocs);
+ mCursor = 0;
+}
+
+bool ShmSegmentsWriter::IsEmpty() const { return mCursor == 0; }
+
+void ShmSegmentsWriter::Clear() {
+ if (mShmAllocator) {
+ IpcResourceUpdateQueue::ReleaseShmems(mShmAllocator, mSmallAllocs);
+ IpcResourceUpdateQueue::ReleaseShmems(mShmAllocator, mLargeAllocs);
+ }
+ mCursor = 0;
+}
+
+ShmSegmentsReader::ShmSegmentsReader(
+ const nsTArray<RefCountedShmem>& aSmallShmems,
+ const nsTArray<ipc::Shmem>& aLargeShmems)
+ : mSmallAllocs(aSmallShmems), mLargeAllocs(aLargeShmems), mChunkSize(0) {
+ if (mSmallAllocs.IsEmpty()) {
+ return;
+ }
+
+ mChunkSize = RefCountedShm::GetSize(mSmallAllocs[0]);
+
+ // Check that all shmems are readable and have the same size. If anything
+ // isn't right, set mChunkSize to zero which signifies that the reader is
+ // in an invalid state and Read calls will return false;
+ for (const auto& shm : mSmallAllocs) {
+ if (!RefCountedShm::IsValid(shm) ||
+ RefCountedShm::GetSize(shm) != mChunkSize ||
+ RefCountedShm::GetBytes(shm) == nullptr) {
+ mChunkSize = 0;
+ return;
+ }
+ }
+
+ for (const auto& shm : mLargeAllocs) {
+ if (!shm.IsReadable() || shm.get<uint8_t>() == nullptr) {
+ mChunkSize = 0;
+ return;
+ }
+ }
+}
+
+bool ShmSegmentsReader::ReadLarge(const layers::OffsetRange& aRange,
+ wr::Vec<uint8_t>& aInto) {
+ // source = zero is for small allocs.
+ MOZ_RELEASE_ASSERT(aRange.source() != 0);
+ if (aRange.source() > mLargeAllocs.Length()) {
+ return false;
+ }
+ size_t id = aRange.source() - 1;
+ const ipc::Shmem& shm = mLargeAllocs[id];
+ if (shm.Size<uint8_t>() < aRange.length()) {
+ return false;
+ }
+
+ uint8_t* srcPtr = shm.get<uint8_t>();
+ aInto.PushBytes(Range<uint8_t>(srcPtr, aRange.length()));
+
+ return true;
+}
+
+bool ShmSegmentsReader::Read(const layers::OffsetRange& aRange,
+ wr::Vec<uint8_t>& aInto) {
+ if (aRange.length() == 0) {
+ return true;
+ }
+
+ if (aRange.source() != 0) {
+ return ReadLarge(aRange, aInto);
+ }
+
+ if (mChunkSize == 0) {
+ return false;
+ }
+
+ if (aRange.start() + aRange.length() > mChunkSize * mSmallAllocs.Length()) {
+ return false;
+ }
+
+ size_t initialLength = aInto.Length();
+
+ size_t srcCursor = aRange.start();
+ size_t remainingBytesToCopy = aRange.length();
+ while (remainingBytesToCopy > 0) {
+ const size_t shm_idx = srcCursor / mChunkSize;
+ const size_t ptrOffset = srcCursor % mChunkSize;
+ const size_t copyRange =
+ std::min(remainingBytesToCopy, mChunkSize - ptrOffset);
+ uint8_t* srcPtr =
+ RefCountedShm::GetBytes(mSmallAllocs[shm_idx]) + ptrOffset;
+
+ aInto.PushBytes(Range<uint8_t>(srcPtr, copyRange));
+
+ srcCursor += copyRange;
+ remainingBytesToCopy -= copyRange;
+ }
+
+ return aInto.Length() - initialLength == aRange.length();
+}
+
+Maybe<Range<uint8_t>> ShmSegmentsReader::GetReadPointerLarge(
+ const layers::OffsetRange& aRange) {
+ // source = zero is for small allocs.
+ MOZ_RELEASE_ASSERT(aRange.source() != 0);
+ if (aRange.source() > mLargeAllocs.Length()) {
+ return Nothing();
+ }
+ size_t id = aRange.source() - 1;
+ const ipc::Shmem& shm = mLargeAllocs[id];
+ if (shm.Size<uint8_t>() < aRange.length()) {
+ return Nothing();
+ }
+
+ uint8_t* srcPtr = shm.get<uint8_t>();
+ return Some(Range<uint8_t>(srcPtr, aRange.length()));
+}
+
+Maybe<Range<uint8_t>> ShmSegmentsReader::GetReadPointer(
+ const layers::OffsetRange& aRange) {
+ if (aRange.length() == 0) {
+ return Some(Range<uint8_t>());
+ }
+
+ if (aRange.source() != 0) {
+ return GetReadPointerLarge(aRange);
+ }
+
+ if (mChunkSize == 0 ||
+ aRange.start() + aRange.length() > mChunkSize * mSmallAllocs.Length()) {
+ return Nothing();
+ }
+
+ size_t srcCursor = aRange.start();
+ size_t remainingBytesToCopy = aRange.length();
+ const size_t shm_idx = srcCursor / mChunkSize;
+ const size_t ptrOffset = srcCursor % mChunkSize;
+ // Return nothing if we can't return a pointer to the full range
+ if (mChunkSize - ptrOffset < remainingBytesToCopy) {
+ return Nothing();
+ }
+ uint8_t* srcPtr = RefCountedShm::GetBytes(mSmallAllocs[shm_idx]) + ptrOffset;
+ return Some(Range<uint8_t>(srcPtr, remainingBytesToCopy));
+}
+
+IpcResourceUpdateQueue::IpcResourceUpdateQueue(
+ layers::WebRenderBridgeChild* aAllocator, size_t aChunkSize)
+ : mWriter(aAllocator, aChunkSize) {}
+
+IpcResourceUpdateQueue::IpcResourceUpdateQueue(
+ IpcResourceUpdateQueue&& aOther) noexcept
+ : mWriter(std::move(aOther.mWriter)),
+ mUpdates(std::move(aOther.mUpdates)) {}
+
+IpcResourceUpdateQueue& IpcResourceUpdateQueue::operator=(
+ IpcResourceUpdateQueue&& aOther) noexcept {
+ MOZ_ASSERT(IsEmpty(), "Will forget existing updates!");
+ mWriter = std::move(aOther.mWriter);
+ mUpdates = std::move(aOther.mUpdates);
+ return *this;
+}
+
+void IpcResourceUpdateQueue::ReplaceResources(IpcResourceUpdateQueue&& aOther) {
+ MOZ_ASSERT(IsEmpty(), "Will forget existing updates!");
+ mWriter = std::move(aOther.mWriter);
+ mUpdates = std::move(aOther.mUpdates);
+}
+
+bool IpcResourceUpdateQueue::AddImage(ImageKey key,
+ const ImageDescriptor& aDescriptor,
+ Range<uint8_t> aBytes) {
+ auto bytes = mWriter.Write(aBytes);
+ if (!bytes.length()) {
+ return false;
+ }
+ mUpdates.AppendElement(layers::OpAddImage(aDescriptor, bytes, 0, key));
+ return true;
+}
+
+bool IpcResourceUpdateQueue::AddBlobImage(BlobImageKey key,
+ const ImageDescriptor& aDescriptor,
+ Range<uint8_t> aBytes,
+ ImageIntRect aVisibleRect) {
+ MOZ_RELEASE_ASSERT(aDescriptor.width > 0 && aDescriptor.height > 0);
+ auto bytes = mWriter.Write(aBytes);
+ if (!bytes.length()) {
+ return false;
+ }
+ mUpdates.AppendElement(
+ layers::OpAddBlobImage(aDescriptor, bytes, aVisibleRect, 0, key));
+ return true;
+}
+
+void IpcResourceUpdateQueue::AddSharedExternalImage(wr::ExternalImageId aExtId,
+ wr::ImageKey aKey) {
+ mUpdates.AppendElement(layers::OpAddSharedExternalImage(aExtId, aKey));
+}
+
+void IpcResourceUpdateQueue::PushExternalImageForTexture(
+ wr::ExternalImageId aExtId, wr::ImageKey aKey,
+ layers::TextureClient* aTexture, bool aIsUpdate) {
+ MOZ_ASSERT(aTexture);
+ MOZ_ASSERT(aTexture->GetIPDLActor());
+ MOZ_RELEASE_ASSERT(aTexture->GetIPDLActor()->GetIPCChannel() ==
+ mWriter.WrBridge()->GetIPCChannel());
+ mUpdates.AppendElement(layers::OpPushExternalImageForTexture(
+ aExtId, aKey, WrapNotNull(aTexture->GetIPDLActor()), aIsUpdate));
+}
+
+bool IpcResourceUpdateQueue::UpdateImageBuffer(
+ ImageKey aKey, const ImageDescriptor& aDescriptor, Range<uint8_t> aBytes) {
+ auto bytes = mWriter.Write(aBytes);
+ if (!bytes.length()) {
+ return false;
+ }
+ mUpdates.AppendElement(layers::OpUpdateImage(aDescriptor, bytes, aKey));
+ return true;
+}
+
+bool IpcResourceUpdateQueue::UpdateBlobImage(BlobImageKey aKey,
+ const ImageDescriptor& aDescriptor,
+ Range<uint8_t> aBytes,
+ ImageIntRect aVisibleRect,
+ ImageIntRect aDirtyRect) {
+ MOZ_ASSERT(aVisibleRect.width > 0 && aVisibleRect.height > 0);
+
+ auto bytes = mWriter.Write(aBytes);
+ if (!bytes.length()) {
+ return false;
+ }
+ mUpdates.AppendElement(layers::OpUpdateBlobImage(aDescriptor, bytes, aKey,
+ aVisibleRect, aDirtyRect));
+ return true;
+}
+
+void IpcResourceUpdateQueue::UpdateSharedExternalImage(
+ wr::ExternalImageId aExtId, wr::ImageKey aKey, ImageIntRect aDirtyRect) {
+ mUpdates.AppendElement(
+ layers::OpUpdateSharedExternalImage(aExtId, aKey, aDirtyRect));
+}
+
+void IpcResourceUpdateQueue::SetBlobImageVisibleArea(
+ wr::BlobImageKey aKey, const ImageIntRect& aArea) {
+ mUpdates.AppendElement(layers::OpSetBlobImageVisibleArea(aArea, aKey));
+}
+
+void IpcResourceUpdateQueue::DeleteImage(ImageKey aKey) {
+ mUpdates.AppendElement(layers::OpDeleteImage(aKey));
+}
+
+void IpcResourceUpdateQueue::DeleteBlobImage(BlobImageKey aKey) {
+ mUpdates.AppendElement(layers::OpDeleteBlobImage(aKey));
+}
+
+bool IpcResourceUpdateQueue::AddRawFont(wr::FontKey aKey, Range<uint8_t> aBytes,
+ uint32_t aIndex) {
+ auto bytes = mWriter.Write(aBytes);
+ if (!bytes.length()) {
+ return false;
+ }
+ mUpdates.AppendElement(layers::OpAddRawFont(bytes, aIndex, aKey));
+ return true;
+}
+
+bool IpcResourceUpdateQueue::AddFontDescriptor(wr::FontKey aKey,
+ Range<uint8_t> aBytes,
+ uint32_t aIndex) {
+ auto bytes = mWriter.Write(aBytes);
+ if (!bytes.length()) {
+ return false;
+ }
+ mUpdates.AppendElement(layers::OpAddFontDescriptor(bytes, aIndex, aKey));
+ return true;
+}
+
+void IpcResourceUpdateQueue::DeleteFont(wr::FontKey aKey) {
+ mUpdates.AppendElement(layers::OpDeleteFont(aKey));
+}
+
+void IpcResourceUpdateQueue::AddFontInstance(
+ wr::FontInstanceKey aKey, wr::FontKey aFontKey, float aGlyphSize,
+ const wr::FontInstanceOptions* aOptions,
+ const wr::FontInstancePlatformOptions* aPlatformOptions,
+ Range<const gfx::FontVariation> aVariations) {
+ auto bytes = mWriter.WriteAsBytes(aVariations);
+ mUpdates.AppendElement(layers::OpAddFontInstance(
+ aOptions ? Some(*aOptions) : Nothing(),
+ aPlatformOptions ? Some(*aPlatformOptions) : Nothing(), bytes, aKey,
+ aFontKey, aGlyphSize));
+}
+
+void IpcResourceUpdateQueue::DeleteFontInstance(wr::FontInstanceKey aKey) {
+ mUpdates.AppendElement(layers::OpDeleteFontInstance(aKey));
+}
+
+void IpcResourceUpdateQueue::Flush(
+ nsTArray<layers::OpUpdateResource>& aUpdates,
+ nsTArray<layers::RefCountedShmem>& aSmallAllocs,
+ nsTArray<ipc::Shmem>& aLargeAllocs) {
+ aUpdates = std::move(mUpdates);
+ mWriter.Flush(aSmallAllocs, aLargeAllocs);
+}
+
+bool IpcResourceUpdateQueue::IsEmpty() const {
+ if (mUpdates.Length() == 0) {
+ MOZ_ASSERT(mWriter.IsEmpty());
+ return true;
+ }
+ return false;
+}
+
+void IpcResourceUpdateQueue::Clear() {
+ mWriter.Clear();
+ mUpdates.Clear();
+}
+
+// static
+void IpcResourceUpdateQueue::ReleaseShmems(
+ ipc::IProtocol* aShmAllocator, nsTArray<layers::RefCountedShmem>& aShms) {
+ for (auto& shm : aShms) {
+ if (RefCountedShm::IsValid(shm) && RefCountedShm::Release(shm) == 0) {
+ RefCountedShm::Dealloc(aShmAllocator, shm);
+ }
+ }
+ aShms.Clear();
+}
+
+// static
+void IpcResourceUpdateQueue::ReleaseShmems(ipc::IProtocol* aShmAllocator,
+ nsTArray<ipc::Shmem>& aShms) {
+ for (auto& shm : aShms) {
+ aShmAllocator->DeallocShmem(shm);
+ }
+ aShms.Clear();
+}
+
+} // namespace wr
+} // namespace mozilla
diff --git a/gfx/layers/wr/IpcResourceUpdateQueue.h b/gfx/layers/wr/IpcResourceUpdateQueue.h
new file mode 100644
index 0000000000..6096ddbddb
--- /dev/null
+++ b/gfx/layers/wr/IpcResourceUpdateQueue.h
@@ -0,0 +1,195 @@
+/* -*- 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/. */
+
+#ifndef GFX_WR_IPCRESOURCEUPDATEQUEUE_H
+#define GFX_WR_IPCRESOURCEUPDATEQUEUE_H
+
+#include "mozilla/layers/WebRenderMessages.h"
+#include "mozilla/layers/RefCountedShmem.h"
+#include "mozilla/layers/TextureClient.h"
+#include "mozilla/webrender/WebRenderTypes.h"
+
+namespace mozilla {
+namespace ipc {
+class IShmemAllocator;
+}
+namespace layers {
+class TextureClient;
+class WebRenderBridgeChild;
+} // namespace layers
+
+namespace wr {
+
+/// ShmSegmentsWriter pushes bytes in a sequence of fixed size shmems for small
+/// allocations and creates dedicated shmems for large allocations.
+class ShmSegmentsWriter {
+ public:
+ ShmSegmentsWriter(layers::WebRenderBridgeChild* aAllocator,
+ size_t aChunkSize);
+ ~ShmSegmentsWriter();
+
+ ShmSegmentsWriter(ShmSegmentsWriter&& aOther) noexcept;
+ ShmSegmentsWriter& operator=(ShmSegmentsWriter&& aOther) noexcept;
+
+ ShmSegmentsWriter(const ShmSegmentsWriter& aOther) = delete;
+ ShmSegmentsWriter& operator=(const ShmSegmentsWriter& aOther) = delete;
+
+ layers::OffsetRange Write(Range<uint8_t> aBytes);
+
+ template <typename T>
+ layers::OffsetRange WriteAsBytes(Range<T> aValues) {
+ return Write(Range<uint8_t>((uint8_t*)aValues.begin().get(),
+ aValues.length() * sizeof(T)));
+ }
+
+ void Flush(nsTArray<layers::RefCountedShmem>& aSmallAllocs,
+ nsTArray<mozilla::ipc::Shmem>& aLargeAllocs);
+
+ void Clear();
+ bool IsEmpty() const;
+
+ layers::WebRenderBridgeChild* WrBridge() const { return mShmAllocator; }
+ size_t ChunkSize() const { return mChunkSize; }
+
+ protected:
+ bool AllocChunk();
+ layers::OffsetRange AllocLargeChunk(size_t aSize);
+
+ nsTArray<layers::RefCountedShmem> mSmallAllocs;
+ nsTArray<mozilla::ipc::Shmem> mLargeAllocs;
+ layers::WebRenderBridgeChild* mShmAllocator;
+ size_t mCursor;
+ size_t mChunkSize;
+};
+
+class ShmSegmentsReader {
+ public:
+ ShmSegmentsReader(const nsTArray<layers::RefCountedShmem>& aSmallShmems,
+ const nsTArray<mozilla::ipc::Shmem>& aLargeShmems);
+
+ bool Read(const layers::OffsetRange& aRange, wr::Vec<uint8_t>& aInto);
+
+ // Get a read pointer, if possible, directly into the shm. If the range has
+ // been broken up into multiple chunks that can't be represented by a single
+ // range, nothing will be returned to indicate failure.
+ Maybe<Range<uint8_t>> GetReadPointer(const layers::OffsetRange& aRange);
+
+ // Get a read pointer, if possible, directly into the shm. Otherwise, copy
+ // it into the Vec and return a pointer to that contiguous memory instead.
+ // If all fails, return nothing.
+ Maybe<Range<uint8_t>> GetReadPointerOrCopy(const layers::OffsetRange& aRange,
+ wr::Vec<uint8_t>& aInto) {
+ if (Maybe<Range<uint8_t>> ptr = GetReadPointer(aRange)) {
+ return ptr;
+ } else {
+ size_t initialLength = aInto.Length();
+ if (Read(aRange, aInto)) {
+ return Some(Range<uint8_t>(aInto.Data() + initialLength,
+ aInto.Length() - initialLength));
+ } else {
+ return Nothing();
+ }
+ }
+ }
+
+ protected:
+ bool ReadLarge(const layers::OffsetRange& aRange, wr::Vec<uint8_t>& aInto);
+
+ Maybe<Range<uint8_t>> GetReadPointerLarge(const layers::OffsetRange& aRange);
+
+ const nsTArray<layers::RefCountedShmem>& mSmallAllocs;
+ const nsTArray<mozilla::ipc::Shmem>& mLargeAllocs;
+ size_t mChunkSize;
+};
+
+class IpcResourceUpdateQueue {
+ public:
+ // Because we are using shmems, the size should be a multiple of the page
+ // size. Each shmem has two guard pages, and the minimum shmem size (at least
+ // one Windows) is 64k which is already quite large for a lot of the resources
+ // we use here. The RefCountedShmem type used to allocate the chunks keeps a
+ // 16 bytes header in the buffer which we account for here as well. So we pick
+ // 64k - 2 * 4k - 16 = 57328 bytes as the default alloc size.
+ explicit IpcResourceUpdateQueue(layers::WebRenderBridgeChild* aAllocator,
+ size_t aChunkSize = 57328);
+
+ IpcResourceUpdateQueue(IpcResourceUpdateQueue&& aOther) noexcept;
+ IpcResourceUpdateQueue& operator=(IpcResourceUpdateQueue&& aOther) noexcept;
+
+ IpcResourceUpdateQueue(const IpcResourceUpdateQueue& aOther) = delete;
+ IpcResourceUpdateQueue& operator=(const IpcResourceUpdateQueue& aOther) =
+ delete;
+
+ // Moves over everything but the subqueues
+ void ReplaceResources(IpcResourceUpdateQueue&& aOther);
+
+ bool AddImage(wr::ImageKey aKey, const ImageDescriptor& aDescriptor,
+ Range<uint8_t> aBytes);
+
+ bool AddBlobImage(wr::BlobImageKey aKey, const ImageDescriptor& aDescriptor,
+ Range<uint8_t> aBytes, ImageIntRect aVisibleRect);
+
+ void AddSharedExternalImage(wr::ExternalImageId aExtId, wr::ImageKey aKey);
+
+ void PushExternalImageForTexture(wr::ExternalImageId aExtId,
+ wr::ImageKey aKey,
+ layers::TextureClient* aTexture,
+ bool aIsUpdate);
+
+ bool UpdateImageBuffer(wr::ImageKey aKey, const ImageDescriptor& aDescriptor,
+ Range<uint8_t> aBytes);
+
+ bool UpdateBlobImage(wr::BlobImageKey aKey,
+ const ImageDescriptor& aDescriptor,
+ Range<uint8_t> aBytes, ImageIntRect aVisibleRect,
+ ImageIntRect aDirtyRect);
+
+ void UpdateSharedExternalImage(ExternalImageId aExtID, ImageKey aKey,
+ ImageIntRect aDirtyRect);
+
+ void SetBlobImageVisibleArea(BlobImageKey aKey, const ImageIntRect& aArea);
+
+ void DeleteImage(wr::ImageKey aKey);
+
+ void DeleteBlobImage(wr::BlobImageKey aKey);
+
+ bool AddRawFont(wr::FontKey aKey, Range<uint8_t> aBytes, uint32_t aIndex);
+
+ bool AddFontDescriptor(wr::FontKey aKey, Range<uint8_t> aBytes,
+ uint32_t aIndex);
+
+ void DeleteFont(wr::FontKey aKey);
+
+ void AddFontInstance(wr::FontInstanceKey aKey, wr::FontKey aFontKey,
+ float aGlyphSize,
+ const wr::FontInstanceOptions* aOptions,
+ const wr::FontInstancePlatformOptions* aPlatformOptions,
+ Range<const gfx::FontVariation> aVariations);
+
+ void DeleteFontInstance(wr::FontInstanceKey aKey);
+
+ void Clear();
+
+ void Flush(nsTArray<layers::OpUpdateResource>& aUpdates,
+ nsTArray<layers::RefCountedShmem>& aSmallAllocs,
+ nsTArray<mozilla::ipc::Shmem>& aLargeAllocs);
+
+ bool IsEmpty() const;
+
+ static void ReleaseShmems(mozilla::ipc::IProtocol*,
+ nsTArray<layers::RefCountedShmem>& aShms);
+ static void ReleaseShmems(mozilla::ipc::IProtocol*,
+ nsTArray<mozilla::ipc::Shmem>& aShms);
+
+ protected:
+ ShmSegmentsWriter mWriter;
+ nsTArray<layers::OpUpdateResource> mUpdates;
+};
+
+} // namespace wr
+} // namespace mozilla
+
+#endif
diff --git a/gfx/layers/wr/OMTAController.cpp b/gfx/layers/wr/OMTAController.cpp
new file mode 100644
index 0000000000..527ec881f6
--- /dev/null
+++ b/gfx/layers/wr/OMTAController.cpp
@@ -0,0 +1,41 @@
+/* -*- 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/OMTAController.h"
+
+#include "mozilla/layers/CompositorBridgeParent.h"
+#include "mozilla/layers/CompositorThread.h"
+#include "mozilla/StaticPrefs_layout.h"
+
+namespace mozilla {
+namespace layers {
+
+void OMTAController::NotifyJankedAnimations(
+ JankedAnimations&& aJankedAnimations) const {
+ if (StaticPrefs::layout_animation_prerender_partial_jank()) {
+ return;
+ }
+
+ if (!CompositorThread()) {
+ return;
+ }
+
+ if (!CompositorThread()->IsOnCurrentThread()) {
+ CompositorThread()->Dispatch(NewRunnableMethod<JankedAnimations&&>(
+ "layers::OMTAController::NotifyJankedAnimations", this,
+ &OMTAController::NotifyJankedAnimations, std::move(aJankedAnimations)));
+ return;
+ }
+
+ if (CompositorBridgeParent* bridge =
+ CompositorBridgeParent::GetCompositorBridgeParentFromLayersId(
+ mRootLayersId)) {
+ bridge->NotifyJankedAnimations(aJankedAnimations);
+ }
+}
+
+} // namespace layers
+} // namespace mozilla
diff --git a/gfx/layers/wr/OMTAController.h b/gfx/layers/wr/OMTAController.h
new file mode 100644
index 0000000000..3119650267
--- /dev/null
+++ b/gfx/layers/wr/OMTAController.h
@@ -0,0 +1,44 @@
+/* -*- 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/. */
+
+#ifndef mozilla_layers_OMTAController_h
+#define mozilla_layers_OMTAController_h
+
+#include <unordered_map>
+
+#include "mozilla/layers/LayersTypes.h" // for LayersId
+#include "nsISerialEventTarget.h"
+#include "nsISupportsImpl.h"
+#include "nsTArrayForwardDeclare.h"
+
+namespace mozilla {
+namespace layers {
+
+/**
+ * This class just delegates the jank animations notification to the compositor
+ * thread from the sampler thread.
+ */
+class OMTAController final {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(OMTAController)
+
+ public:
+ explicit OMTAController(LayersId aRootLayersId)
+ : mRootLayersId(aRootLayersId) {}
+
+ using JankedAnimations =
+ std::unordered_map<LayersId, nsTArray<uint64_t>, LayersId::HashFn>;
+ void NotifyJankedAnimations(JankedAnimations&& aJankedAnimations) const;
+
+ private:
+ ~OMTAController() = default;
+
+ LayersId mRootLayersId;
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif // mozilla_layers_OMTAController_h
diff --git a/gfx/layers/wr/OMTASampler.cpp b/gfx/layers/wr/OMTASampler.cpp
new file mode 100644
index 0000000000..c9616e87e1
--- /dev/null
+++ b/gfx/layers/wr/OMTASampler.cpp
@@ -0,0 +1,248 @@
+/* -*- 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/OMTASampler.h"
+
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/layers/CompositorAnimationStorage.h"
+#include "mozilla/layers/CompositorThread.h"
+#include "mozilla/layers/OMTAController.h"
+#include "mozilla/layers/SynchronousTask.h"
+#include "mozilla/layers/WebRenderBridgeParent.h"
+#include "mozilla/webrender/WebRenderAPI.h"
+
+namespace mozilla {
+namespace layers {
+
+StaticMutex OMTASampler::sWindowIdLock;
+StaticAutoPtr<std::unordered_map<uint64_t, RefPtr<OMTASampler>>>
+ OMTASampler::sWindowIdMap;
+
+OMTASampler::OMTASampler(const RefPtr<CompositorAnimationStorage>& aAnimStorage,
+ LayersId aRootLayersId)
+ : mAnimStorage(aAnimStorage),
+ mStorageLock("OMTASampler::mStorageLock"),
+ mThreadIdLock("OMTASampler::mThreadIdLock"),
+ mSampleTimeLock("OMTASampler::mSampleTimeLock"),
+ mIsInTestMode(false) {
+ mController = new OMTAController(aRootLayersId);
+}
+
+void OMTASampler::Destroy() {
+ StaticMutexAutoLock lock(sWindowIdLock);
+ if (mWindowId) {
+ MOZ_ASSERT(sWindowIdMap);
+ sWindowIdMap->erase(wr::AsUint64(*mWindowId));
+ }
+}
+
+void OMTASampler::SetWebRenderWindowId(const wr::WrWindowId& aWindowId) {
+ StaticMutexAutoLock lock(sWindowIdLock);
+ MOZ_ASSERT(!mWindowId);
+ mWindowId = Some(aWindowId);
+ if (!sWindowIdMap) {
+ sWindowIdMap = new std::unordered_map<uint64_t, RefPtr<OMTASampler>>();
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction("OMTASampler::ClearOnShutdown",
+ [] { ClearOnShutdown(&sWindowIdMap); }));
+ }
+ (*sWindowIdMap)[wr::AsUint64(aWindowId)] = this;
+}
+
+/*static*/
+void OMTASampler::SetSamplerThread(const wr::WrWindowId& aWindowId) {
+ if (RefPtr<OMTASampler> sampler = GetSampler(aWindowId)) {
+ MutexAutoLock lock(sampler->mThreadIdLock);
+ sampler->mSamplerThreadId = Some(PlatformThread::CurrentId());
+ }
+}
+
+/*static*/
+void OMTASampler::Sample(const wr::WrWindowId& aWindowId,
+ wr::Transaction* aTransaction) {
+ if (RefPtr<OMTASampler> sampler = GetSampler(aWindowId)) {
+ wr::TransactionWrapper txn(aTransaction);
+ sampler->Sample(txn);
+ }
+}
+
+void OMTASampler::SetSampleTime(const TimeStamp& aSampleTime) {
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+
+ const bool hasAnimations = HasAnimations();
+
+ MutexAutoLock lock(mSampleTimeLock);
+
+ // Reset the previous time stamp if we don't already have any running
+ // animations to avoid using the time which is far behind for newly
+ // started animations.
+ mPreviousSampleTime = hasAnimations ? std::move(mSampleTime) : TimeStamp();
+ mSampleTime = aSampleTime;
+}
+
+void OMTASampler::ResetPreviousSampleTime() {
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ MutexAutoLock lock(mSampleTimeLock);
+
+ mPreviousSampleTime = TimeStamp();
+}
+
+void OMTASampler::Sample(wr::TransactionWrapper& aTxn) {
+ MOZ_ASSERT(IsSamplerThread());
+
+ // If we are in test mode, don't sample with the current time stamp, it will
+ // skew cached animation values.
+ if (mIsInTestMode) {
+ return;
+ }
+
+ TimeStamp sampleTime;
+ TimeStamp previousSampleTime;
+ { // scope lock
+ MutexAutoLock lock(mSampleTimeLock);
+
+ // If mSampleTime is null we're in a startup phase where the
+ // WebRenderBridgeParent hasn't yet provided us with a sample time.
+ // If we're that early there probably aren't any OMTA animations happening
+ // anyway, so using Timestamp::Now() should be fine.
+ sampleTime = mSampleTime.IsNull() ? TimeStamp::Now() : mSampleTime;
+ previousSampleTime = mPreviousSampleTime;
+ }
+
+ WrAnimations animations = SampleAnimations(previousSampleTime, sampleTime);
+
+ aTxn.AppendDynamicProperties(animations.mOpacityArrays,
+ animations.mTransformArrays,
+ animations.mColorArrays);
+}
+
+WrAnimations OMTASampler::SampleAnimations(const TimeStamp& aPreviousSampleTime,
+ const TimeStamp& aSampleTime) {
+ MOZ_ASSERT(IsSamplerThread());
+
+ MutexAutoLock lock(mStorageLock);
+
+ mAnimStorage->SampleAnimations(mController, aPreviousSampleTime, aSampleTime);
+
+ return mAnimStorage->CollectWebRenderAnimations();
+}
+
+OMTAValue OMTASampler::GetOMTAValue(const uint64_t& aId) const {
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ MutexAutoLock lock(mStorageLock);
+
+ return mAnimStorage->GetOMTAValue(aId);
+}
+
+void OMTASampler::SampleForTesting(const Maybe<TimeStamp>& aTestingSampleTime) {
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+
+ TimeStamp sampleTime;
+ TimeStamp previousSampleTime;
+ { // scope lock
+ MutexAutoLock timeLock(mSampleTimeLock);
+ if (aTestingSampleTime) {
+ // If we are on testing refresh mode, use the testing time stamp for both
+ // of the previous sample time and the current sample time since unlike
+ // normal refresh mode, the testing mode animations on the compositor are
+ // synchronously composed, so we don't need to worry about the time gap
+ // between the main thread and compositor thread.
+ sampleTime = *aTestingSampleTime;
+ previousSampleTime = *aTestingSampleTime;
+ } else {
+ sampleTime = mSampleTime;
+ previousSampleTime = mPreviousSampleTime;
+ }
+ }
+
+ MutexAutoLock storageLock(mStorageLock);
+ mAnimStorage->SampleAnimations(mController, previousSampleTime, sampleTime);
+}
+
+void OMTASampler::SetAnimations(
+ uint64_t aId, const LayersId& aLayersId,
+ const nsTArray<layers::Animation>& aAnimations) {
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ MutexAutoLock lock(mStorageLock);
+
+ mAnimStorage->SetAnimations(aId, aLayersId, aAnimations);
+}
+
+bool OMTASampler::HasAnimations() const {
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ MutexAutoLock lock(mStorageLock);
+
+ return mAnimStorage->HasAnimations();
+}
+
+void OMTASampler::ClearActiveAnimations(
+ std::unordered_map<uint64_t, wr::WrEpoch>& aActiveAnimations) {
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ MutexAutoLock lock(mStorageLock);
+ for (const auto& id : aActiveAnimations) {
+ mAnimStorage->ClearById(id.first);
+ }
+}
+
+void OMTASampler::RemoveEpochDataPriorTo(
+ std::queue<CompositorAnimationIdsForEpoch>& aCompositorAnimationsToDelete,
+ std::unordered_map<uint64_t, wr::WrEpoch>& aActiveAnimations,
+ const wr::WrEpoch& aRenderedEpoch) {
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ MutexAutoLock lock(mStorageLock);
+
+ while (!aCompositorAnimationsToDelete.empty()) {
+ if (aRenderedEpoch < aCompositorAnimationsToDelete.front().mEpoch) {
+ break;
+ }
+ for (uint64_t id : aCompositorAnimationsToDelete.front().mIds) {
+ const auto activeAnim = aActiveAnimations.find(id);
+ if (activeAnim == aActiveAnimations.end()) {
+ NS_ERROR("Tried to delete invalid animation");
+ continue;
+ }
+ // Check if animation delete request is still valid.
+ if (activeAnim->second <= aCompositorAnimationsToDelete.front().mEpoch) {
+ mAnimStorage->ClearById(id);
+ aActiveAnimations.erase(activeAnim);
+ }
+ }
+ aCompositorAnimationsToDelete.pop();
+ }
+}
+
+bool OMTASampler::IsSamplerThread() const {
+ MutexAutoLock lock(mThreadIdLock);
+ return mSamplerThreadId && PlatformThread::CurrentId() == *mSamplerThreadId;
+}
+
+/*static*/
+already_AddRefed<OMTASampler> OMTASampler::GetSampler(
+ const wr::WrWindowId& aWindowId) {
+ RefPtr<OMTASampler> sampler;
+ StaticMutexAutoLock lock(sWindowIdLock);
+ if (sWindowIdMap) {
+ auto it = sWindowIdMap->find(wr::AsUint64(aWindowId));
+ if (it != sWindowIdMap->end()) {
+ sampler = it->second;
+ }
+ }
+ return sampler.forget();
+}
+
+} // namespace layers
+} // namespace mozilla
+
+void omta_register_sampler(mozilla::wr::WrWindowId aWindowId) {
+ mozilla::layers::OMTASampler::SetSamplerThread(aWindowId);
+}
+
+void omta_sample(mozilla::wr::WrWindowId aWindowId,
+ mozilla::wr::Transaction* aTransaction) {
+ mozilla::layers::OMTASampler::Sample(aWindowId, aTransaction);
+}
+
+void omta_deregister_sampler(mozilla::wr::WrWindowId aWindowId) {}
diff --git a/gfx/layers/wr/OMTASampler.h b/gfx/layers/wr/OMTASampler.h
new file mode 100644
index 0000000000..513c905fa1
--- /dev/null
+++ b/gfx/layers/wr/OMTASampler.h
@@ -0,0 +1,156 @@
+/* -*- 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/. */
+
+#ifndef mozilla_layers_OMTASampler_h
+#define mozilla_layers_OMTASampler_h
+
+#include <unordered_map>
+#include <queue>
+
+#include "base/platform_thread.h" // for PlatformThreadId
+#include "mozilla/layers/OMTAController.h" // for OMTAController
+#include "mozilla/Atomics.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/webrender/WebRenderTypes.h" // For WrWindowId, WrEpoch, etc.
+
+namespace mozilla {
+
+class TimeStamp;
+
+namespace wr {
+struct Transaction;
+class TransactionWrapper;
+} // namespace wr
+
+namespace layers {
+class Animation;
+class CompositorAnimationStorage;
+class OMTAValue;
+struct CompositorAnimationIdsForEpoch;
+struct LayersId;
+struct WrAnimations;
+
+/**
+ * This interface exposes OMTA methods related to "sampling" (i.e. calculating
+ * animating values) and "". All sampling methods should be called on the
+ * sampler thread, all some of them should be called on the compositor thread.
+ */
+class OMTASampler final {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(OMTASampler)
+
+ public:
+ OMTASampler(const RefPtr<CompositorAnimationStorage>& aAnimStorage,
+ LayersId aRootLayersId);
+
+ // Whoever creates this sampler is responsible for calling Destroy() on it
+ // before releasing the owning refptr.
+ void Destroy();
+
+ void SetWebRenderWindowId(const wr::WrWindowId& aWindowId);
+
+ /**
+ * This function is invoked from rust on the render backend thread when it
+ * is created. It effectively tells the OMTASampler "the current thread is
+ * the sampler thread for this window id" and allows OMTASampler to remember
+ * which thread it is.
+ */
+ static void SetSamplerThread(const wr::WrWindowId& aWindowId);
+
+ static void Sample(const wr::WrWindowId& aWindowId, wr::Transaction* aTxn);
+
+ /**
+ * Sample all animations, called on the sampler thread.
+ */
+ void Sample(wr::TransactionWrapper& aTxn);
+
+ /**
+ * These funtions get called on the the compositor thread.
+ */
+ void SetSampleTime(const TimeStamp& aSampleTime);
+ void ResetPreviousSampleTime();
+ void SetAnimations(uint64_t aId, const LayersId& aLayersId,
+ const nsTArray<layers::Animation>& aAnimations);
+ bool HasAnimations() const;
+
+ /**
+ * Clear AnimatedValues and Animations data, called on the compositor
+ * thread.
+ */
+ void ClearActiveAnimations(
+ std::unordered_map<uint64_t, wr::Epoch>& aActiveAnimations);
+ void RemoveEpochDataPriorTo(
+ std::queue<CompositorAnimationIdsForEpoch>& aCompositorAnimationsToDelete,
+ std::unordered_map<uint64_t, wr::Epoch>& aActiveAnimations,
+ const wr::Epoch& aRenderedEpoch);
+
+ // Those two methods are for testing called on the compositor thread.
+ OMTAValue GetOMTAValue(const uint64_t& aId) const;
+ /**
+ * There are two possibilities when this function gets called, either 1) in
+ * testing refesh driver mode or 2) in normal refresh driver mode. In the case
+ * of 2) |aTestingSampleTime| should be Nothing() so that we can use
+ * |mPreviousSampleTime| and |mSampleTime| for sampling animations.
+ */
+ void SampleForTesting(const Maybe<TimeStamp>& aTestingSampleTime);
+
+ /**
+ * Returns true if currently on the "sampler thread".
+ */
+ bool IsSamplerThread() const;
+
+ void EnterTestMode() { mIsInTestMode = true; }
+ void LeaveTestMode() { mIsInTestMode = false; }
+
+ protected:
+ ~OMTASampler() = default;
+
+ static already_AddRefed<OMTASampler> GetSampler(
+ const wr::WrWindowId& aWindowId);
+
+ private:
+ WrAnimations SampleAnimations(const TimeStamp& aPreviousSampleTime,
+ const TimeStamp& aSampleTime);
+
+ RefPtr<OMTAController> mController;
+ // Can only be accessed or modified while holding mStorageLock.
+ RefPtr<CompositorAnimationStorage> mAnimStorage;
+ mutable Mutex mStorageLock MOZ_UNANNOTATED;
+
+ // Used to manage the mapping from a WR window id to OMTASampler. These are
+ // only used if WebRender is enabled. Both sWindowIdMap and mWindowId should
+ // only be used while holding the sWindowIdLock. Note that we use a
+ // StaticAutoPtr wrapper on sWindowIdMap to avoid a static initializer for the
+ // unordered_map. This also avoids the initializer/memory allocation in cases
+ // where we're not using WebRender.
+ static StaticMutex sWindowIdLock MOZ_UNANNOTATED;
+ static StaticAutoPtr<std::unordered_map<uint64_t, RefPtr<OMTASampler>>>
+ sWindowIdMap;
+ Maybe<wr::WrWindowId> mWindowId;
+
+ // Lock used to protected mSamplerThreadId
+ mutable Mutex mThreadIdLock MOZ_UNANNOTATED;
+ // If WebRender is enabled, this holds the thread id of the render backend
+ // thread (which is the sampler thread) for the compositor associated with
+ // this OMTASampler instance.
+ Maybe<PlatformThreadId> mSamplerThreadId;
+
+ Mutex mSampleTimeLock MOZ_UNANNOTATED;
+ // Can only be accessed or modified while holding mSampleTimeLock.
+ TimeStamp mSampleTime;
+ // Same as |mSampleTime|, can only be accessed or modified while holding
+ // mSampleTimeLock.
+ // We basically use this time stamp instead of |mSampleTime| to make
+ // animations more in sync with other animations on the main thread.
+ TimeStamp mPreviousSampleTime;
+ Atomic<bool> mIsInTestMode;
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif // mozilla_layers_OMTASampler_h
diff --git a/gfx/layers/wr/RenderRootStateManager.cpp b/gfx/layers/wr/RenderRootStateManager.cpp
new file mode 100644
index 0000000000..8ddaa601a9
--- /dev/null
+++ b/gfx/layers/wr/RenderRootStateManager.cpp
@@ -0,0 +1,210 @@
+/* -*- 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/RenderRootStateManager.h"
+
+#include "mozilla/layers/WebRenderBridgeChild.h"
+#include "mozilla/layers/WebRenderLayerManager.h"
+
+namespace mozilla {
+namespace layers {
+
+// RenderRootStateManager shares its ref count with the WebRenderLayerManager
+// that created it. You can think of the two classes as being one unit, except
+// there are multiple RenderRootStateManagers per WebRenderLayerManager. Since
+// we need to reference the WebRenderLayerManager and it needs to reference us,
+// this avoids us needing to involve the cycle collector.
+void RenderRootStateManager::AddRef() { mLayerManager->AddRef(); }
+
+void RenderRootStateManager::Release() { mLayerManager->Release(); }
+
+WebRenderBridgeChild* RenderRootStateManager::WrBridge() const {
+ return mLayerManager->WrBridge();
+}
+
+WebRenderCommandBuilder& RenderRootStateManager::CommandBuilder() {
+ return mLayerManager->CommandBuilder();
+}
+
+RenderRootStateManager::WebRenderUserDataRefTable*
+RenderRootStateManager::GetWebRenderUserDataTable() {
+ return mLayerManager->GetWebRenderUserDataTable();
+}
+
+wr::IpcResourceUpdateQueue& RenderRootStateManager::AsyncResourceUpdates() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mAsyncResourceUpdates) {
+ mAsyncResourceUpdates.emplace(WrBridge());
+
+ RefPtr<Runnable> task = NewRunnableMethod(
+ "RenderRootStateManager::FlushAsyncResourceUpdates", this,
+ &RenderRootStateManager::FlushAsyncResourceUpdates);
+ NS_DispatchToMainThread(task.forget());
+ }
+
+ return mAsyncResourceUpdates.ref();
+}
+
+void RenderRootStateManager::Destroy() {
+ ClearAsyncAnimations();
+
+ if (WrBridge()) {
+ // Just clear ImageKeys, they are deleted during WebRenderAPI destruction.
+ DiscardLocalImages();
+ // CompositorAnimations are cleared by WebRenderBridgeParent.
+ mDiscardedCompositorAnimationsIds.Clear();
+ }
+
+ mActiveCompositorAnimationIds.clear();
+
+ mDestroyed = true;
+}
+
+void RenderRootStateManager::FlushAsyncResourceUpdates() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mAsyncResourceUpdates) {
+ return;
+ }
+
+ if (!IsDestroyed() && WrBridge()) {
+ WrBridge()->UpdateResources(mAsyncResourceUpdates.ref());
+ }
+
+ mAsyncResourceUpdates.reset();
+}
+
+void RenderRootStateManager::AddImageKeyForDiscard(wr::ImageKey key) {
+ mImageKeysToDelete.AppendElement(key);
+}
+
+void RenderRootStateManager::AddBlobImageKeyForDiscard(wr::BlobImageKey key) {
+ mBlobImageKeysToDelete.AppendElement(key);
+}
+
+void RenderRootStateManager::DiscardImagesInTransaction(
+ wr::IpcResourceUpdateQueue& aResources) {
+ for (const auto& key : mImageKeysToDelete) {
+ aResources.DeleteImage(key);
+ }
+ for (const auto& key : mBlobImageKeysToDelete) {
+ aResources.DeleteBlobImage(key);
+ }
+ mImageKeysToDelete.Clear();
+ mBlobImageKeysToDelete.Clear();
+}
+
+void RenderRootStateManager::DiscardLocalImages() {
+ // Removes images but doesn't tell the parent side about them
+ // This is useful in empty / failed transactions where we created
+ // image keys but didn't tell the parent about them yet.
+ mImageKeysToDelete.Clear();
+ mBlobImageKeysToDelete.Clear();
+}
+
+void RenderRootStateManager::ClearCachedResources() {
+ mActiveCompositorAnimationIds.clear();
+ mDiscardedCompositorAnimationsIds.Clear();
+}
+
+void RenderRootStateManager::AddActiveCompositorAnimationId(uint64_t aId) {
+ // In layers-free mode we track the active compositor animation ids on the
+ // client side so that we don't try to discard the same animation id multiple
+ // times. We could just ignore the multiple-discard on the parent side, but
+ // checking on the content side reduces IPC traffic.
+ mActiveCompositorAnimationIds.insert(aId);
+}
+
+void RenderRootStateManager::AddCompositorAnimationsIdForDiscard(uint64_t aId) {
+ if (mActiveCompositorAnimationIds.erase(aId)) {
+ // For layers-free ensure we don't try to discard an animation id that
+ // wasn't active. We also remove it from mActiveCompositorAnimationIds so we
+ // don't discard it again unless it gets re-activated.
+ mDiscardedCompositorAnimationsIds.AppendElement(aId);
+ }
+}
+
+void RenderRootStateManager::DiscardCompositorAnimations() {
+ if (WrBridge()->IPCOpen() && !mDiscardedCompositorAnimationsIds.IsEmpty()) {
+ WrBridge()->SendDeleteCompositorAnimations(
+ mDiscardedCompositorAnimationsIds);
+ }
+ mDiscardedCompositorAnimationsIds.Clear();
+}
+
+void RenderRootStateManager::RegisterAsyncAnimation(
+ const wr::ImageKey& aKey, SharedSurfacesAnimation* aAnimation) {
+ mAsyncAnimations.insert(std::make_pair(wr::AsUint64(aKey), aAnimation));
+}
+
+void RenderRootStateManager::DeregisterAsyncAnimation(
+ const wr::ImageKey& aKey) {
+ mAsyncAnimations.erase(wr::AsUint64(aKey));
+}
+
+void RenderRootStateManager::ClearAsyncAnimations() {
+ for (const auto& i : mAsyncAnimations) {
+ i.second->Invalidate(this);
+ }
+ mAsyncAnimations.clear();
+}
+
+void RenderRootStateManager::WrReleasedImages(
+ const nsTArray<wr::ExternalImageKeyPair>& aPairs) {
+ // A SharedSurfaceAnimation object's lifetime is tied to its owning
+ // ImageContainer. When the ImageContainer is released,
+ // SharedSurfaceAnimation::Destroy is called which should ensure it is removed
+ // from the layer manager. Whenever the namespace for the
+ // WebRenderLayerManager itself is invalidated (e.g. we changed windows, or
+ // were destroyed ourselves), we callback into the SharedSurfaceAnimation
+ // object to remove its image key for us and any bound surfaces. If, for any
+ // reason, we somehow missed an WrReleasedImages call before the animation
+ // was bound to the layer manager, it will free those associated surfaces on
+ // the next ReleasePreviousFrame call.
+ for (const auto& pair : aPairs) {
+ auto i = mAsyncAnimations.find(wr::AsUint64(pair.key));
+ if (i != mAsyncAnimations.end()) {
+ i->second->ReleasePreviousFrame(this, pair.id);
+ }
+ }
+}
+
+void RenderRootStateManager::AddWebRenderParentCommand(
+ const WebRenderParentCommand& aCmd) {
+ WrBridge()->AddWebRenderParentCommand(aCmd);
+}
+void RenderRootStateManager::UpdateResources(
+ wr::IpcResourceUpdateQueue& aResources) {
+ WrBridge()->UpdateResources(aResources);
+}
+void RenderRootStateManager::AddPipelineIdForCompositable(
+ const wr::PipelineId& aPipelineId, const CompositableHandle& aHandle,
+ CompositableHandleOwner aOwner) {
+ WrBridge()->AddPipelineIdForCompositable(aPipelineId, aHandle, aOwner);
+}
+void RenderRootStateManager::RemovePipelineIdForCompositable(
+ const wr::PipelineId& aPipelineId) {
+ WrBridge()->RemovePipelineIdForCompositable(aPipelineId);
+}
+/// Release TextureClient that is bounded to ImageKey.
+/// It is used for recycling TextureClient.
+void RenderRootStateManager::ReleaseTextureOfImage(const wr::ImageKey& aKey) {
+ WrBridge()->ReleaseTextureOfImage(aKey);
+}
+
+Maybe<wr::FontInstanceKey> RenderRootStateManager::GetFontKeyForScaledFont(
+ gfx::ScaledFont* aScaledFont, wr::IpcResourceUpdateQueue& aResources) {
+ return WrBridge()->GetFontKeyForScaledFont(aScaledFont, aResources);
+}
+
+Maybe<wr::FontKey> RenderRootStateManager::GetFontKeyForUnscaledFont(
+ gfx::UnscaledFont* aUnscaledFont, wr::IpcResourceUpdateQueue& aResources) {
+ return WrBridge()->GetFontKeyForUnscaledFont(aUnscaledFont, aResources);
+}
+
+} // namespace layers
+} // namespace mozilla
diff --git a/gfx/layers/wr/RenderRootStateManager.h b/gfx/layers/wr/RenderRootStateManager.h
new file mode 100644
index 0000000000..6c0221776e
--- /dev/null
+++ b/gfx/layers/wr/RenderRootStateManager.h
@@ -0,0 +1,97 @@
+/* -*- 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/. */
+
+#ifndef GFX_RENDERROOTSTATEMANAGER_H
+#define GFX_RENDERROOTSTATEMANAGER_H
+
+#include "mozilla/webrender/WebRenderAPI.h"
+
+#include "mozilla/layers/IpcResourceUpdateQueue.h"
+#include "mozilla/layers/SharedSurfacesChild.h"
+#include "mozilla/layers/WebRenderCommandBuilder.h"
+#include "nsTHashSet.h"
+
+namespace mozilla {
+
+namespace layers {
+
+class RenderRootStateManager {
+ typedef nsTHashSet<RefPtr<WebRenderUserData>> WebRenderUserDataRefTable;
+
+ public:
+ void AddRef();
+ void Release();
+
+ RenderRootStateManager() : mLayerManager(nullptr), mDestroyed(false) {}
+
+ void Destroy();
+ bool IsDestroyed() { return mDestroyed; }
+ wr::IpcResourceUpdateQueue& AsyncResourceUpdates();
+ WebRenderBridgeChild* WrBridge() const;
+ WebRenderCommandBuilder& CommandBuilder();
+ WebRenderUserDataRefTable* GetWebRenderUserDataTable();
+ WebRenderLayerManager* LayerManager() { return mLayerManager; }
+
+ void AddImageKeyForDiscard(wr::ImageKey key);
+ void AddBlobImageKeyForDiscard(wr::BlobImageKey key);
+ void DiscardImagesInTransaction(wr::IpcResourceUpdateQueue& aResources);
+ void DiscardLocalImages();
+
+ void ClearCachedResources();
+
+ // Methods to manage the compositor animation ids. Active animations are still
+ // going, and when they end we discard them and remove them from the active
+ // list.
+ void AddActiveCompositorAnimationId(uint64_t aId);
+ void AddCompositorAnimationsIdForDiscard(uint64_t aId);
+ void DiscardCompositorAnimations();
+
+ void RegisterAsyncAnimation(const wr::ImageKey& aKey,
+ SharedSurfacesAnimation* aAnimation);
+ void DeregisterAsyncAnimation(const wr::ImageKey& aKey);
+ void ClearAsyncAnimations();
+ void WrReleasedImages(const nsTArray<wr::ExternalImageKeyPair>& aPairs);
+
+ void AddWebRenderParentCommand(const WebRenderParentCommand& aCmd);
+ void UpdateResources(wr::IpcResourceUpdateQueue& aResources);
+ void AddPipelineIdForCompositable(const wr::PipelineId& aPipelineId,
+ const CompositableHandle& aHandle,
+ CompositableHandleOwner aOwner);
+ void RemovePipelineIdForCompositable(const wr::PipelineId& aPipelineId);
+ /// Release TextureClient that is bounded to ImageKey.
+ /// It is used for recycling TextureClient.
+ void ReleaseTextureOfImage(const wr::ImageKey& aKey);
+ Maybe<wr::FontInstanceKey> GetFontKeyForScaledFont(
+ gfx::ScaledFont* aScaledFont, wr::IpcResourceUpdateQueue& aResources);
+ Maybe<wr::FontKey> GetFontKeyForUnscaledFont(
+ gfx::UnscaledFont* aUnscaledFont, wr::IpcResourceUpdateQueue& aResources);
+
+ void FlushAsyncResourceUpdates();
+
+ private:
+ WebRenderLayerManager* mLayerManager;
+ Maybe<wr::IpcResourceUpdateQueue> mAsyncResourceUpdates;
+ nsTArray<wr::ImageKey> mImageKeysToDelete;
+ nsTArray<wr::BlobImageKey> mBlobImageKeysToDelete;
+ std::unordered_map<uint64_t, RefPtr<SharedSurfacesAnimation>>
+ mAsyncAnimations;
+
+ // Set of compositor animation ids for which there are active animations (as
+ // of the last transaction) on the compositor side.
+ std::unordered_set<uint64_t> mActiveCompositorAnimationIds;
+ // Compositor animation ids for animations that are done now and that we want
+ // the compositor to discard information for.
+ nsTArray<uint64_t> mDiscardedCompositorAnimationsIds;
+
+ bool mDestroyed;
+
+ friend class WebRenderLayerManager;
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif /* GFX_RENDERROOTSTATEMANAGER_H */
diff --git a/gfx/layers/wr/RenderRootTypes.cpp b/gfx/layers/wr/RenderRootTypes.cpp
new file mode 100644
index 0000000000..d572a6fd74
--- /dev/null
+++ b/gfx/layers/wr/RenderRootTypes.cpp
@@ -0,0 +1,108 @@
+/* -*- 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 "RenderRootTypes.h"
+#include "mozilla/layers/WebRenderMessageUtils.h"
+#include "mozilla/layers/WebRenderBridgeChild.h"
+
+namespace mozilla {
+namespace ipc {
+
+void IPDLParamTraits<mozilla::layers::DisplayListData>::Write(
+ IPC::MessageWriter* aWriter, IProtocol* aActor, paramType&& aParam) {
+ WriteIPDLParam(aWriter, aActor, aParam.mIdNamespace);
+ WriteIPDLParam(aWriter, aActor, aParam.mRect);
+ WriteIPDLParam(aWriter, aActor, aParam.mCommands);
+ WriteIPDLParam(aWriter, aActor, std::move(aParam.mDLItems));
+ WriteIPDLParam(aWriter, aActor, std::move(aParam.mDLCache));
+ WriteIPDLParam(aWriter, aActor, std::move(aParam.mDLSpatialTree));
+ WriteIPDLParam(aWriter, aActor, aParam.mDLDesc);
+ WriteIPDLParam(aWriter, aActor, aParam.mResourceUpdates);
+ WriteIPDLParam(aWriter, aActor, aParam.mSmallShmems);
+ WriteIPDLParam(aWriter, aActor, std::move(aParam.mLargeShmems));
+ WriteIPDLParam(aWriter, aActor, aParam.mScrollData);
+}
+
+bool IPDLParamTraits<mozilla::layers::DisplayListData>::Read(
+ IPC::MessageReader* aReader, IProtocol* aActor, paramType* aResult) {
+ if (ReadIPDLParam(aReader, aActor, &aResult->mIdNamespace) &&
+ ReadIPDLParam(aReader, aActor, &aResult->mRect) &&
+ ReadIPDLParam(aReader, aActor, &aResult->mCommands) &&
+ ReadIPDLParam(aReader, aActor, &aResult->mDLItems) &&
+ ReadIPDLParam(aReader, aActor, &aResult->mDLCache) &&
+ ReadIPDLParam(aReader, aActor, &aResult->mDLSpatialTree) &&
+ ReadIPDLParam(aReader, aActor, &aResult->mDLDesc) &&
+ ReadIPDLParam(aReader, aActor, &aResult->mResourceUpdates) &&
+ ReadIPDLParam(aReader, aActor, &aResult->mSmallShmems) &&
+ ReadIPDLParam(aReader, aActor, &aResult->mLargeShmems) &&
+ ReadIPDLParam(aReader, aActor, &aResult->mScrollData)) {
+ return true;
+ }
+ return false;
+}
+
+void WriteScrollUpdates(IPC::MessageWriter* aWriter, IProtocol* aActor,
+ layers::ScrollUpdatesMap& aParam) {
+ // ICK: we need to manually serialize this map because
+ // nsDataHashTable doesn't support it (and other maps cause other issues)
+ WriteIPDLParam(aWriter, aActor, aParam.Count());
+ for (auto it = aParam.ConstIter(); !it.Done(); it.Next()) {
+ WriteIPDLParam(aWriter, aActor, it.Key());
+ WriteIPDLParam(aWriter, aActor, it.Data());
+ }
+}
+
+bool ReadScrollUpdates(IPC::MessageReader* aReader, IProtocol* aActor,
+ layers::ScrollUpdatesMap* aResult) {
+ // Manually deserialize mScrollUpdates as a stream of K,V pairs
+ uint32_t count;
+ if (!ReadIPDLParam(aReader, aActor, &count)) {
+ return false;
+ }
+
+ layers::ScrollUpdatesMap map(count);
+ for (size_t i = 0; i < count; ++i) {
+ layers::ScrollableLayerGuid::ViewID key;
+ nsTArray<mozilla::ScrollPositionUpdate> data;
+ if (!ReadIPDLParam(aReader, aActor, &key) ||
+ !ReadIPDLParam(aReader, aActor, &data)) {
+ return false;
+ }
+ map.InsertOrUpdate(key, std::move(data));
+ }
+
+ MOZ_RELEASE_ASSERT(map.Count() == count);
+ *aResult = std::move(map);
+ return true;
+}
+
+void IPDLParamTraits<mozilla::layers::TransactionData>::Write(
+ IPC::MessageWriter* aWriter, IProtocol* aActor, paramType&& aParam) {
+ WriteIPDLParam(aWriter, aActor, aParam.mIdNamespace);
+ WriteIPDLParam(aWriter, aActor, aParam.mCommands);
+ WriteIPDLParam(aWriter, aActor, aParam.mResourceUpdates);
+ WriteIPDLParam(aWriter, aActor, aParam.mSmallShmems);
+ WriteIPDLParam(aWriter, aActor, std::move(aParam.mLargeShmems));
+ WriteScrollUpdates(aWriter, aActor, aParam.mScrollUpdates);
+ WriteIPDLParam(aWriter, aActor, aParam.mPaintSequenceNumber);
+}
+
+bool IPDLParamTraits<mozilla::layers::TransactionData>::Read(
+ IPC::MessageReader* aReader, IProtocol* aActor, paramType* aResult) {
+ if (ReadIPDLParam(aReader, aActor, &aResult->mIdNamespace) &&
+ ReadIPDLParam(aReader, aActor, &aResult->mCommands) &&
+ ReadIPDLParam(aReader, aActor, &aResult->mResourceUpdates) &&
+ ReadIPDLParam(aReader, aActor, &aResult->mSmallShmems) &&
+ ReadIPDLParam(aReader, aActor, &aResult->mLargeShmems) &&
+ ReadScrollUpdates(aReader, aActor, &aResult->mScrollUpdates) &&
+ ReadIPDLParam(aReader, aActor, &aResult->mPaintSequenceNumber)) {
+ return true;
+ }
+ return false;
+}
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/gfx/layers/wr/RenderRootTypes.h b/gfx/layers/wr/RenderRootTypes.h
new file mode 100644
index 0000000000..2cf970852c
--- /dev/null
+++ b/gfx/layers/wr/RenderRootTypes.h
@@ -0,0 +1,74 @@
+/* -*- 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/. */
+
+#ifndef GFX_RENDERROOTTYPES_H
+#define GFX_RENDERROOTTYPES_H
+
+#include "mozilla/webrender/WebRenderAPI.h"
+#include "mozilla/webrender/WebRenderTypes.h"
+#include "mozilla/layers/WebRenderMessages.h"
+#include "mozilla/layers/WebRenderScrollData.h"
+
+namespace mozilla {
+
+namespace layers {
+
+struct DisplayListData {
+ wr::IdNamespace mIdNamespace;
+ LayoutDeviceRect mRect;
+ nsTArray<WebRenderParentCommand> mCommands;
+ Maybe<mozilla::ipc::ByteBuf> mDLItems;
+ Maybe<mozilla::ipc::ByteBuf> mDLCache;
+ Maybe<mozilla::ipc::ByteBuf> mDLSpatialTree;
+ wr::BuiltDisplayListDescriptor mDLDesc;
+ nsTArray<OpUpdateResource> mResourceUpdates;
+ nsTArray<RefCountedShmem> mSmallShmems;
+ nsTArray<mozilla::ipc::Shmem> mLargeShmems;
+ Maybe<WebRenderScrollData> mScrollData;
+};
+
+struct TransactionData {
+ wr::IdNamespace mIdNamespace;
+ nsTArray<WebRenderParentCommand> mCommands;
+ nsTArray<OpUpdateResource> mResourceUpdates;
+ nsTArray<RefCountedShmem> mSmallShmems;
+ nsTArray<mozilla::ipc::Shmem> mLargeShmems;
+ ScrollUpdatesMap mScrollUpdates;
+ uint32_t mPaintSequenceNumber;
+};
+
+typedef Maybe<TransactionData> MaybeTransactionData;
+
+} // namespace layers
+
+namespace ipc {
+
+template <>
+struct IPDLParamTraits<mozilla::layers::DisplayListData> {
+ typedef mozilla::layers::DisplayListData paramType;
+
+ static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor,
+ paramType&& aParam);
+
+ static bool Read(IPC::MessageReader* aReader, IProtocol* aActor,
+ paramType* aResult);
+};
+
+template <>
+struct IPDLParamTraits<mozilla::layers::TransactionData> {
+ typedef mozilla::layers::TransactionData paramType;
+
+ static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor,
+ paramType&& aParam);
+
+ static bool Read(IPC::MessageReader* aReader, IProtocol* aActor,
+ paramType* aResult);
+};
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif /* GFX_RENDERROOTTYPES_H */
diff --git a/gfx/layers/wr/StackingContextHelper.cpp b/gfx/layers/wr/StackingContextHelper.cpp
new file mode 100644
index 0000000000..d5ffbf26a0
--- /dev/null
+++ b/gfx/layers/wr/StackingContextHelper.cpp
@@ -0,0 +1,276 @@
+/* -*- 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/StackingContextHelper.h"
+
+#include "mozilla/PresShell.h"
+#include "mozilla/gfx/Point.h"
+#include "mozilla/gfx/Matrix.h"
+#include "UnitTransforms.h"
+#include "nsDisplayList.h"
+#include "mozilla/dom/BrowserChild.h"
+#include "nsLayoutUtils.h"
+#include "ActiveLayerTracker.h"
+
+namespace mozilla {
+namespace layers {
+using namespace gfx;
+
+StackingContextHelper::StackingContextHelper()
+ : mBuilder(nullptr),
+ mScale(1.0f, 1.0f),
+ mAffectsClipPositioning(false),
+ mDeferredTransformItem(nullptr),
+ mRasterizeLocally(false) {
+ // mOrigin remains at 0,0
+}
+
+static nsSize ComputeDesiredDisplaySizeForAnimation(nsIFrame* aContainerFrame) {
+ // Use the size of the nearest widget as the maximum size. This
+ // is important since it might be a popup that is bigger than the
+ // pres context's size.
+ nsPresContext* presContext = aContainerFrame->PresContext();
+ nsIWidget* widget = aContainerFrame->GetNearestWidget();
+ if (widget) {
+ return LayoutDevicePixel::ToAppUnits(widget->GetClientSize(),
+ presContext->AppUnitsPerDevPixel());
+ }
+
+ return presContext->GetVisibleArea().Size();
+}
+
+/* static */
+MatrixScales ChooseScale(nsIFrame* aContainerFrame,
+ nsDisplayItem* aContainerItem,
+ const nsRect& aVisibleRect, float aXScale,
+ float aYScale, const Matrix& aTransform2d,
+ bool aCanDraw2D) {
+ MatrixScales scale;
+ // XXX Should we do something for 3D transforms?
+ if (aCanDraw2D && !aContainerFrame->Combines3DTransformWithAncestors() &&
+ !aContainerFrame->HasPerspective()) {
+ // If the container's transform is animated off main thread, fix a suitable
+ // scale size for animation
+ if (aContainerItem &&
+ aContainerItem->GetType() == DisplayItemType::TYPE_TRANSFORM &&
+ // FIXME: What we need is only transform, rotate, and scale, not
+ // translate, so it's be better to use a property set, instead of
+ // display item type here.
+ EffectCompositor::HasAnimationsForCompositor(
+ aContainerFrame, DisplayItemType::TYPE_TRANSFORM)) {
+ nsSize displaySize =
+ ComputeDesiredDisplaySizeForAnimation(aContainerFrame);
+ // compute scale using the animation on the container, taking ancestors in
+ // to account
+ nsSize scaledVisibleSize = nsSize(aVisibleRect.Width() * aXScale,
+ aVisibleRect.Height() * aYScale);
+ scale = nsLayoutUtils::ComputeSuitableScaleForAnimation(
+ aContainerFrame, scaledVisibleSize, displaySize);
+ // multiply by the scale inherited from ancestors--we use a uniform
+ // scale factor to prevent blurring when the layer is rotated.
+ float incomingScale = std::max(aXScale, aYScale);
+ scale = scale * ScaleFactor<UnknownUnits, UnknownUnits>(incomingScale);
+ } else {
+ // Scale factors are normalized to a power of 2 to reduce the number of
+ // resolution changes
+ scale = (aTransform2d * gfx::Matrix::Scaling(aXScale, aYScale))
+ .ScaleFactors();
+ // For frames with a changing scale transform round scale factors up to
+ // nearest power-of-2 boundary so that we don't keep having to redraw
+ // the content as it scales up and down. Rounding up to nearest
+ // power-of-2 boundary ensures we never scale up, only down --- avoiding
+ // jaggies. It also ensures we never scale down by more than a factor of
+ // 2, avoiding bad downscaling quality.
+ Matrix frameTransform;
+ if (ActiveLayerTracker::IsScaleSubjectToAnimation(aContainerFrame)) {
+ scale.xScale = gfxUtils::ClampToScaleFactor(scale.xScale);
+ scale.yScale = gfxUtils::ClampToScaleFactor(scale.yScale);
+
+ // Limit animated scale factors to not grow excessively beyond the
+ // display size.
+ nsSize maxScale(4, 4);
+ if (!aVisibleRect.IsEmpty()) {
+ nsSize displaySize =
+ ComputeDesiredDisplaySizeForAnimation(aContainerFrame);
+ maxScale = Max(maxScale, displaySize / aVisibleRect.Size());
+ }
+ if (scale.xScale > maxScale.width) {
+ scale.xScale = gfxUtils::ClampToScaleFactor(maxScale.width, true);
+ }
+ if (scale.yScale > maxScale.height) {
+ scale.yScale = gfxUtils::ClampToScaleFactor(maxScale.height, true);
+ }
+ } else {
+ // XXX Do we need to move nearly-integer values to integers here?
+ }
+ }
+ // If the scale factors are too small, just use 1.0. The content is being
+ // scaled out of sight anyway.
+ if (fabs(scale.xScale) < 1e-8 || fabs(scale.yScale) < 1e-8) {
+ scale = MatrixScales(1.0, 1.0);
+ }
+ } else {
+ scale = MatrixScales(1.0, 1.0);
+ }
+
+ // Prevent the scale from getting too large, to avoid excessive memory
+ // allocation. Usually memory allocation is limited by the visible region,
+ // which should be restricted to the display port. But at very large scales
+ // the visible region itself can become excessive due to rounding errors.
+ // Clamping the scale here prevents that.
+ return MatrixScales(std::min(scale.xScale, 32768.0f),
+ std::min(scale.yScale, 32768.0f));
+}
+
+StackingContextHelper::StackingContextHelper(
+ const StackingContextHelper& aParentSC, const ActiveScrolledRoot* aAsr,
+ nsIFrame* aContainerFrame, nsDisplayItem* aContainerItem,
+ wr::DisplayListBuilder& aBuilder, const wr::StackingContextParams& aParams,
+ const LayoutDeviceRect& aBounds)
+ : mBuilder(&aBuilder),
+ mScale(1.0f, 1.0f),
+ mDeferredTransformItem(aParams.mDeferredTransformItem),
+ mRasterizeLocally(aParams.mRasterizeLocally ||
+ aParentSC.mRasterizeLocally) {
+ MOZ_ASSERT(!aContainerItem || aContainerItem->CreatesStackingContextHelper());
+
+ mOrigin = aParentSC.mOrigin + aBounds.TopLeft();
+ // Compute scale for fallback rendering. We don't try to guess a scale for 3d
+ // transformed items
+
+ if (aParams.mBoundTransform) {
+ gfx::Matrix transform2d;
+ bool canDraw2D = aParams.mBoundTransform->CanDraw2D(&transform2d);
+ if (canDraw2D &&
+ aParams.reference_frame_kind != wr::WrReferenceFrameKind::Perspective &&
+ !aContainerFrame->Combines3DTransformWithAncestors()) {
+ mInheritedTransform = transform2d * aParentSC.mInheritedTransform;
+
+ int32_t apd = aContainerFrame->PresContext()->AppUnitsPerDevPixel();
+ nsRect r = LayoutDevicePixel::ToAppUnits(aBounds, apd);
+ mScale = ChooseScale(aContainerFrame, aContainerItem, r,
+ aParentSC.mScale.xScale, aParentSC.mScale.yScale,
+ transform2d,
+ /* aCanDraw2D = */ true);
+ } else {
+ mScale = gfx::MatrixScales(1.0f, 1.0f);
+ mInheritedTransform = gfx::Matrix::Scaling(1.f, 1.f);
+ }
+
+ if (aParams.mAnimated) {
+ mSnappingSurfaceTransform = gfx::Matrix::Scaling(mScale);
+ } else {
+ mSnappingSurfaceTransform =
+ transform2d * aParentSC.mSnappingSurfaceTransform;
+ }
+
+ } else if (aParams.reference_frame_kind ==
+ wr::WrReferenceFrameKind::Transform &&
+ aContainerItem &&
+ aContainerItem->GetType() == DisplayItemType::TYPE_ASYNC_ZOOM &&
+ aContainerItem->Frame()) {
+ float resolution = aContainerItem->Frame()->PresShell()->GetResolution();
+ gfx::Matrix transform = gfx::Matrix::Scaling(resolution, resolution);
+
+ mInheritedTransform = transform * aParentSC.mInheritedTransform;
+ mScale =
+ ScaleFactor<UnknownUnits, UnknownUnits>(resolution) * aParentSC.mScale;
+
+ MOZ_ASSERT(!aParams.mAnimated);
+ mSnappingSurfaceTransform = transform * aParentSC.mSnappingSurfaceTransform;
+
+ } else if (!aAsr && !aContainerFrame && !aContainerItem &&
+ aParams.mRootReferenceFrame) {
+ // this is the root stacking context helper
+ Scale2D resolution;
+
+ // If we are in a remote browser, then apply scaling from ancestor browsers
+ if (mozilla::dom::BrowserChild* browserChild =
+ mozilla::dom::BrowserChild::GetFrom(
+ aParams.mRootReferenceFrame->PresShell())) {
+ resolution = browserChild->GetEffectsInfo().mRasterScale;
+ }
+
+ gfx::Matrix transform =
+ gfx::Matrix::Scaling(resolution.xScale, resolution.yScale);
+
+ mInheritedTransform = transform * aParentSC.mInheritedTransform;
+ mScale = aParentSC.mScale * resolution;
+
+ MOZ_ASSERT(!aParams.mAnimated);
+ mSnappingSurfaceTransform = transform * aParentSC.mSnappingSurfaceTransform;
+
+ } else {
+ mInheritedTransform = aParentSC.mInheritedTransform;
+ mScale = aParentSC.mScale;
+ }
+
+ auto rasterSpace =
+ mRasterizeLocally
+ ? wr::RasterSpace::Local(std::max(mScale.xScale, mScale.yScale))
+ : wr::RasterSpace::Screen();
+
+ MOZ_ASSERT(!aParams.clip.IsNone());
+ mReferenceFrameId = mBuilder->PushStackingContext(
+ aParams, wr::ToLayoutRect(aBounds), rasterSpace);
+
+ if (mReferenceFrameId) {
+ mSpaceAndClipChainHelper.emplace(aBuilder, mReferenceFrameId.ref());
+ }
+
+ mAffectsClipPositioning =
+ mReferenceFrameId.isSome() || (aBounds.TopLeft() != LayoutDevicePoint());
+
+ // If the parent stacking context has a deferred transform item, inherit it
+ // into this stacking context, as long as the ASR hasn't changed. Refer to
+ // the comments on StackingContextHelper::mDeferredTransformItem for an
+ // explanation of what goes in these fields.
+ if (aParentSC.mDeferredTransformItem &&
+ aAsr == aParentSC.mDeferredTransformItem->GetActiveScrolledRoot()) {
+ if (mDeferredTransformItem) {
+ // If we are deferring another transform, put the combined transform from
+ // all the ancestor deferred items into mDeferredAncestorTransform
+ mDeferredAncestorTransform = aParentSC.GetDeferredTransformMatrix();
+ } else {
+ // We are not deferring another transform, so we can just inherit the
+ // parent stacking context's deferred data without any modification.
+ mDeferredTransformItem = aParentSC.mDeferredTransformItem;
+ mDeferredAncestorTransform = aParentSC.mDeferredAncestorTransform;
+ }
+ }
+}
+
+StackingContextHelper::~StackingContextHelper() {
+ if (mBuilder) {
+ mSpaceAndClipChainHelper.reset();
+ mBuilder->PopStackingContext(mReferenceFrameId.isSome());
+ }
+}
+
+nsDisplayTransform* StackingContextHelper::GetDeferredTransformItem() const {
+ return mDeferredTransformItem;
+}
+
+Maybe<gfx::Matrix4x4> StackingContextHelper::GetDeferredTransformMatrix()
+ const {
+ if (mDeferredTransformItem) {
+ // See the comments on StackingContextHelper::mDeferredTransformItem for
+ // an explanation of what's stored in mDeferredTransformItem and
+ // mDeferredAncestorTransform. Here we need to return the combined transform
+ // transform from all the deferred ancestors, including
+ // mDeferredTransformItem.
+ gfx::Matrix4x4 result = mDeferredTransformItem->GetTransform().GetMatrix();
+ if (mDeferredAncestorTransform) {
+ result = result * *mDeferredAncestorTransform;
+ }
+ return Some(result);
+ } else {
+ return Nothing();
+ }
+}
+
+} // namespace layers
+} // namespace mozilla
diff --git a/gfx/layers/wr/StackingContextHelper.h b/gfx/layers/wr/StackingContextHelper.h
new file mode 100644
index 0000000000..368449a2fd
--- /dev/null
+++ b/gfx/layers/wr/StackingContextHelper.h
@@ -0,0 +1,129 @@
+/* -*- 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/. */
+
+#ifndef GFX_STACKINGCONTEXTHELPER_H
+#define GFX_STACKINGCONTEXTHELPER_H
+
+#include "mozilla/Attributes.h"
+#include "mozilla/gfx/MatrixFwd.h"
+#include "mozilla/webrender/WebRenderAPI.h"
+#include "mozilla/webrender/WebRenderTypes.h"
+#include "Units.h"
+
+namespace mozilla {
+
+class nsDisplayTransform;
+struct ActiveScrolledRoot;
+
+namespace layers {
+
+/**
+ * This is a helper class that pushes/pops a stacking context, and manages
+ * some of the coordinate space transformations needed.
+ */
+class MOZ_RAII StackingContextHelper {
+ public:
+ StackingContextHelper(const StackingContextHelper& aParentSC,
+ const ActiveScrolledRoot* aAsr,
+ nsIFrame* aContainerFrame,
+ nsDisplayItem* aContainerItem,
+ wr::DisplayListBuilder& aBuilder,
+ const wr::StackingContextParams& aParams,
+ const LayoutDeviceRect& aBounds = LayoutDeviceRect());
+
+ // This version of the constructor should only be used at the root level
+ // of the tree, so that we have a StackingContextHelper to pass down into
+ // the RenderLayer traversal, but don't actually want it to push a stacking
+ // context on the display list builder.
+ StackingContextHelper();
+
+ // Pops the stacking context, if one was pushed during the constructor.
+ ~StackingContextHelper();
+
+ // Export the inherited scale
+ gfx::MatrixScales GetInheritedScale() const { return mScale; }
+
+ const gfx::Matrix& GetInheritedTransform() const {
+ return mInheritedTransform;
+ }
+
+ const gfx::Matrix& GetSnappingSurfaceTransform() const {
+ return mSnappingSurfaceTransform;
+ }
+
+ nsDisplayTransform* GetDeferredTransformItem() const;
+ Maybe<gfx::Matrix4x4> GetDeferredTransformMatrix() const;
+
+ bool AffectsClipPositioning() const { return mAffectsClipPositioning; }
+ Maybe<wr::WrSpatialId> ReferenceFrameId() const { return mReferenceFrameId; }
+
+ const LayoutDevicePoint& GetOrigin() const { return mOrigin; }
+
+ private:
+ wr::DisplayListBuilder* mBuilder;
+ gfx::MatrixScales mScale;
+ gfx::Matrix mInheritedTransform;
+ LayoutDevicePoint mOrigin;
+
+ // The "snapping surface" defines the space that we want to snap in.
+ // You can think of it as the nearest physical surface.
+ // Animated transforms create a new snapping surface, so that changes to their
+ // transform don't affect the snapping of their contents. Non-animated
+ // transforms do *not* create a new snapping surface, so that for example the
+ // existence of a non-animated identity transform does not affect snapping.
+ gfx::Matrix mSnappingSurfaceTransform;
+ bool mAffectsClipPositioning;
+ Maybe<wr::WrSpatialId> mReferenceFrameId;
+ Maybe<wr::SpaceAndClipChainHelper> mSpaceAndClipChainHelper;
+
+ // The deferred transform item is used when building the WebRenderScrollData
+ // structure. The backstory is that APZ needs to know about transforms that
+ // apply to the different APZC instances. Prior to bug 1423370, we would do
+ // this by creating a new WebRenderLayerScrollData for each nsDisplayTransform
+ // item we encountered. However, this was unnecessarily expensive because it
+ // turned out a lot of nsDisplayTransform items didn't have new ASRs defined
+ // as descendants, so we'd create the WebRenderLayerScrollData and send it
+ // over to APZ even though the transform information was not needed in that
+ // case.
+ //
+ // In bug 1423370 and friends, this was optimized by "deferring" a
+ // nsDisplayTransform item when we encountered it during display list
+ // traversal. If we found a descendant of that transform item that had a
+ // new ASR or otherwise was "relevant to APZ", we would then pluck the
+ // transform matrix off the deferred item and put it on the
+ // WebRenderLayerScrollData instance created for that APZ-relevant descendant.
+ //
+ // One complication with this is if there are multiple nsDisplayTransform
+ // items in the ancestor chain for the APZ-relevant item. As we traverse the
+ // display list, we will defer the outermost nsDisplayTransform item, and when
+ // we encounter the next one we will need to merge it with the already-
+ // deferred one somehow. What we do in this case is have
+ // mDeferredTransformItem always point to the "innermost" deferred transform
+ // item (i.e. the closest ancestor nsDisplayTransform item of the item that
+ // created this StackingContextHelper). And then we use
+ // mDeferredAncestorTransform to store the product of all the other transforms
+ // that were deferred. As a result, there is an invariant here that if
+ // mDeferredTransformItem is nullptr, mDeferredAncestorTransform will also
+ // be Nothing(). Note that we can only do this if the nsDisplayTransform items
+ // share the same ASR. If we are processing an nsDisplayTransform item with a
+ // different ASR than the previously-deferred item, we assume that the
+ // previously-deferred transform will get sent to APZ as part of a separate
+ // WebRenderLayerScrollData item, and so we don't need to bother with any
+ // merging. (The merging probably wouldn't even make sense because the
+ // coordinate spaces might be different in the face of async scrolling). This
+ // behaviour of forcing a WebRenderLayerScrollData item to be generated when
+ // the ASR changes is implemented in
+ // WebRenderCommandBuilder::CreateWebRenderCommandsFromDisplayList.
+ nsDisplayTransform* mDeferredTransformItem;
+ Maybe<gfx::Matrix4x4> mDeferredAncestorTransform;
+
+ bool mRasterizeLocally;
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif /* GFX_STACKINGCONTEXTHELPER_H */
diff --git a/gfx/layers/wr/WebRenderBridgeChild.cpp b/gfx/layers/wr/WebRenderBridgeChild.cpp
new file mode 100644
index 0000000000..2c5868007a
--- /dev/null
+++ b/gfx/layers/wr/WebRenderBridgeChild.cpp
@@ -0,0 +1,586 @@
+/* -*- 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/WebRenderBridgeChild.h"
+
+#include "gfxPlatform.h"
+#include "mozilla/StaticPrefs_gfx.h"
+#include "mozilla/layers/CompositableClient.h"
+#include "mozilla/layers/CompositorBridgeChild.h"
+#include "mozilla/layers/ImageDataSerializer.h"
+#include "mozilla/layers/IpcResourceUpdateQueue.h"
+#include "mozilla/layers/StackingContextHelper.h"
+#include "mozilla/layers/PTextureChild.h"
+#include "mozilla/layers/WebRenderLayerManager.h"
+#include "mozilla/webrender/WebRenderAPI.h"
+#include "PDMFactory.h"
+
+namespace mozilla {
+namespace layers {
+
+using namespace mozilla::gfx;
+
+WebRenderBridgeChild::WebRenderBridgeChild(const wr::PipelineId& aPipelineId)
+ : mIsInTransaction(false),
+ mIsInClearCachedResources(false),
+ mIdNamespace{0},
+ mResourceId(0),
+ mPipelineId(aPipelineId),
+ mManager(nullptr),
+ mIPCOpen(false),
+ mDestroyed(false),
+ mSentDisplayList(false),
+ mFontKeysDeleted(0),
+ mFontInstanceKeysDeleted(0) {}
+
+WebRenderBridgeChild::~WebRenderBridgeChild() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mDestroyed);
+}
+
+void WebRenderBridgeChild::Destroy(bool aIsSync) {
+ if (!IPCOpen()) {
+ return;
+ }
+
+ DoDestroy();
+
+ if (aIsSync) {
+ SendShutdownSync();
+ } else {
+ SendShutdown();
+ }
+}
+
+void WebRenderBridgeChild::ActorDestroy(ActorDestroyReason why) { DoDestroy(); }
+
+void WebRenderBridgeChild::DoDestroy() {
+ if (RefCountedShm::IsValid(mResourceShm) &&
+ RefCountedShm::Release(mResourceShm) == 0) {
+ RefCountedShm::Dealloc(this, mResourceShm);
+ mResourceShm = RefCountedShmem();
+ }
+
+ // mDestroyed is used to prevent calling Send__delete__() twice.
+ // When this function is called from CompositorBridgeChild::Destroy().
+ mDestroyed = true;
+ mManager = nullptr;
+}
+
+void WebRenderBridgeChild::AddWebRenderParentCommand(
+ const WebRenderParentCommand& aCmd) {
+ mParentCommands.AppendElement(aCmd);
+}
+
+void WebRenderBridgeChild::BeginTransaction() {
+ MOZ_ASSERT(!mDestroyed);
+
+ UpdateFwdTransactionId();
+ mIsInTransaction = true;
+}
+
+void WebRenderBridgeChild::UpdateResources(
+ wr::IpcResourceUpdateQueue& aResources) {
+ if (!IPCOpen()) {
+ aResources.Clear();
+ return;
+ }
+
+ if (aResources.IsEmpty()) {
+ return;
+ }
+
+ nsTArray<OpUpdateResource> resourceUpdates;
+ nsTArray<RefCountedShmem> smallShmems;
+ nsTArray<ipc::Shmem> largeShmems;
+ aResources.Flush(resourceUpdates, smallShmems, largeShmems);
+
+ this->SendUpdateResources(mIdNamespace, resourceUpdates, smallShmems,
+ std::move(largeShmems));
+}
+
+bool WebRenderBridgeChild::EndTransaction(
+ DisplayListData&& aDisplayListData, TransactionId aTransactionId,
+ bool aContainsSVGGroup, const mozilla::VsyncId& aVsyncId,
+ const mozilla::TimeStamp& aVsyncStartTime,
+ const mozilla::TimeStamp& aRefreshStartTime,
+ const mozilla::TimeStamp& aTxnStartTime, const nsCString& aTxnURL) {
+ MOZ_ASSERT(!mDestroyed);
+ MOZ_ASSERT(mIsInTransaction);
+
+ TimeStamp fwdTime = TimeStamp::Now();
+
+ aDisplayListData.mCommands = std::move(mParentCommands);
+ aDisplayListData.mIdNamespace = mIdNamespace;
+
+ nsTArray<CompositionPayload> payloads;
+ if (mManager) {
+ mManager->TakeCompositionPayloads(payloads);
+ }
+
+ mSentDisplayList = true;
+ bool ret = this->SendSetDisplayList(
+ std::move(aDisplayListData), mDestroyedActors, GetFwdTransactionId(),
+ aTransactionId, aContainsSVGGroup, aVsyncId, aVsyncStartTime,
+ aRefreshStartTime, aTxnStartTime, aTxnURL, fwdTime, payloads);
+
+ // With multiple render roots, we may not have sent all of our
+ // mParentCommands, so go ahead and go through our mParentCommands and ensure
+ // they get sent.
+ ProcessWebRenderParentCommands();
+ mDestroyedActors.Clear();
+ mIsInTransaction = false;
+
+ return ret;
+}
+
+void WebRenderBridgeChild::EndEmptyTransaction(
+ const FocusTarget& aFocusTarget, Maybe<TransactionData>&& aTransactionData,
+ TransactionId aTransactionId, const mozilla::VsyncId& aVsyncId,
+ const mozilla::TimeStamp& aVsyncStartTime,
+ const mozilla::TimeStamp& aRefreshStartTime,
+ const mozilla::TimeStamp& aTxnStartTime, const nsCString& aTxnURL) {
+ MOZ_ASSERT(!mDestroyed);
+ MOZ_ASSERT(mIsInTransaction);
+
+ TimeStamp fwdTime = TimeStamp::Now();
+
+ if (aTransactionData) {
+ aTransactionData->mCommands = std::move(mParentCommands);
+ }
+
+ nsTArray<CompositionPayload> payloads;
+ if (mManager) {
+ mManager->TakeCompositionPayloads(payloads);
+ }
+
+ this->SendEmptyTransaction(
+ aFocusTarget, std::move(aTransactionData), mDestroyedActors,
+ GetFwdTransactionId(), aTransactionId, aVsyncId, aVsyncStartTime,
+ aRefreshStartTime, aTxnStartTime, aTxnURL, fwdTime, payloads);
+
+ // With multiple render roots, we may not have sent all of our
+ // mParentCommands, so go ahead and go through our mParentCommands and ensure
+ // they get sent.
+ ProcessWebRenderParentCommands();
+ mDestroyedActors.Clear();
+ mIsInTransaction = false;
+}
+
+void WebRenderBridgeChild::ProcessWebRenderParentCommands() {
+ MOZ_ASSERT(!mDestroyed);
+
+ if (!mParentCommands.IsEmpty()) {
+ this->SendParentCommands(mIdNamespace, mParentCommands);
+ mParentCommands.Clear();
+ }
+}
+
+void WebRenderBridgeChild::AddPipelineIdForCompositable(
+ const wr::PipelineId& aPipelineId, const CompositableHandle& aHandle,
+ CompositableHandleOwner aOwner) {
+ AddWebRenderParentCommand(
+ OpAddPipelineIdForCompositable(aPipelineId, aHandle, aOwner));
+}
+
+void WebRenderBridgeChild::RemovePipelineIdForCompositable(
+ const wr::PipelineId& aPipelineId) {
+ AddWebRenderParentCommand(OpRemovePipelineIdForCompositable(aPipelineId));
+}
+
+wr::ExternalImageId WebRenderBridgeChild::GetNextExternalImageId() {
+ wr::MaybeExternalImageId id =
+ GetCompositorBridgeChild()->GetNextExternalImageId();
+ MOZ_RELEASE_ASSERT(id.isSome());
+ return id.value();
+}
+
+void WebRenderBridgeChild::ReleaseTextureOfImage(const wr::ImageKey& aKey) {
+ AddWebRenderParentCommand(OpReleaseTextureOfImage(aKey));
+}
+
+struct FontFileDataSink {
+ wr::FontKey* mFontKey;
+ WebRenderBridgeChild* mWrBridge;
+ wr::IpcResourceUpdateQueue* mResources;
+};
+
+static void WriteFontFileData(const uint8_t* aData, uint32_t aLength,
+ uint32_t aIndex, void* aBaton) {
+ FontFileDataSink* sink = static_cast<FontFileDataSink*>(aBaton);
+
+ *sink->mFontKey = sink->mWrBridge->GetNextFontKey();
+
+ sink->mResources->AddRawFont(
+ *sink->mFontKey, Range<uint8_t>(const_cast<uint8_t*>(aData), aLength),
+ aIndex);
+}
+
+static void WriteFontDescriptor(const uint8_t* aData, uint32_t aLength,
+ uint32_t aIndex, void* aBaton) {
+ FontFileDataSink* sink = static_cast<FontFileDataSink*>(aBaton);
+
+ *sink->mFontKey = sink->mWrBridge->GetNextFontKey();
+
+ sink->mResources->AddFontDescriptor(
+ *sink->mFontKey, Range<uint8_t>(const_cast<uint8_t*>(aData), aLength),
+ aIndex);
+}
+
+void WebRenderBridgeChild::PushGlyphs(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ Range<const wr::GlyphInstance> aGlyphs, gfx::ScaledFont* aFont,
+ const wr::ColorF& aColor, const StackingContextHelper& aSc,
+ const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
+ bool aBackfaceVisible, const wr::GlyphOptions* aGlyphOptions) {
+ MOZ_ASSERT(aFont);
+
+ Maybe<wr::WrFontInstanceKey> key = GetFontKeyForScaledFont(aFont, aResources);
+ MOZ_ASSERT(key.isSome());
+
+ if (key.isSome()) {
+ aBuilder.PushText(aBounds, aClip, aBackfaceVisible, aColor, key.value(),
+ aGlyphs, aGlyphOptions);
+ }
+}
+
+Maybe<wr::FontInstanceKey> WebRenderBridgeChild::GetFontKeyForScaledFont(
+ gfx::ScaledFont* aScaledFont, wr::IpcResourceUpdateQueue& aResources) {
+ MOZ_ASSERT(!mDestroyed);
+ MOZ_ASSERT(aScaledFont);
+ MOZ_ASSERT(aScaledFont->CanSerialize());
+
+ return mFontInstanceKeys.WithEntryHandle(
+ aScaledFont, [&](auto&& entry) -> Maybe<wr::FontInstanceKey> {
+ if (!entry) {
+ Maybe<wr::FontKey> fontKey = GetFontKeyForUnscaledFont(
+ aScaledFont->GetUnscaledFont(), aResources);
+ if (fontKey.isNothing()) {
+ return Nothing();
+ }
+
+ wr::FontInstanceKey instanceKey = GetNextFontInstanceKey();
+
+ Maybe<wr::FontInstanceOptions> options;
+ Maybe<wr::FontInstancePlatformOptions> platformOptions;
+ std::vector<FontVariation> variations;
+ aScaledFont->GetWRFontInstanceOptions(&options, &platformOptions,
+ &variations);
+
+ aResources.AddFontInstance(
+ instanceKey, fontKey.value(), aScaledFont->GetSize(),
+ options.ptrOr(nullptr), platformOptions.ptrOr(nullptr),
+ Range<const FontVariation>(variations.data(), variations.size()));
+
+ entry.Insert(instanceKey);
+ }
+
+ return Some(*entry);
+ });
+}
+
+Maybe<wr::FontKey> WebRenderBridgeChild::GetFontKeyForUnscaledFont(
+ gfx::UnscaledFont* aUnscaled, wr::IpcResourceUpdateQueue& aResources) {
+ MOZ_ASSERT(!mDestroyed);
+
+ return mFontKeys.WithEntryHandle(
+ aUnscaled, [&](auto&& entry) -> Maybe<wr::FontKey> {
+ if (!entry) {
+ wr::FontKey fontKey = {wr::IdNamespace{0}, 0};
+ FontFileDataSink sink = {&fontKey, this, &aResources};
+ // First try to retrieve a descriptor for the font, as this is much
+ // cheaper to send over IPC than the full raw font data. If this is
+ // not possible, then and only then fall back to getting the raw font
+ // file data. If that fails, then the only thing left to do is signal
+ // failure by returning a null font key.
+ if (!aUnscaled->GetFontDescriptor(WriteFontDescriptor, &sink) &&
+ !aUnscaled->GetFontFileData(WriteFontFileData, &sink)) {
+ return Nothing();
+ }
+
+ entry.Insert(fontKey);
+ }
+
+ return Some(*entry);
+ });
+}
+
+void WebRenderBridgeChild::RemoveExpiredFontKeys(
+ wr::IpcResourceUpdateQueue& aResourceUpdates) {
+ uint32_t counter = gfx::ScaledFont::DeletionCounter();
+ if (mFontInstanceKeysDeleted != counter) {
+ mFontInstanceKeysDeleted = counter;
+ for (auto iter = mFontInstanceKeys.Iter(); !iter.Done(); iter.Next()) {
+ if (!iter.Key()) {
+ aResourceUpdates.DeleteFontInstance(iter.Data());
+ iter.Remove();
+ }
+ }
+ }
+ counter = gfx::UnscaledFont::DeletionCounter();
+ if (mFontKeysDeleted != counter) {
+ mFontKeysDeleted = counter;
+ for (auto iter = mFontKeys.Iter(); !iter.Done(); iter.Next()) {
+ if (!iter.Key()) {
+ aResourceUpdates.DeleteFont(iter.Data());
+ iter.Remove();
+ }
+ }
+ }
+}
+
+CompositorBridgeChild* WebRenderBridgeChild::GetCompositorBridgeChild() {
+ if (!IPCOpen()) {
+ return nullptr;
+ }
+ return static_cast<CompositorBridgeChild*>(Manager());
+}
+
+TextureForwarder* WebRenderBridgeChild::GetTextureForwarder() {
+ return static_cast<TextureForwarder*>(GetCompositorBridgeChild());
+}
+
+LayersIPCActor* WebRenderBridgeChild::GetLayersIPCActor() {
+ return static_cast<LayersIPCActor*>(GetCompositorBridgeChild());
+}
+
+void WebRenderBridgeChild::SyncWithCompositor(
+ const Maybe<uint64_t>& aWindowID) {
+ if (!IPCOpen()) {
+ return;
+ }
+ SendSyncWithCompositor();
+}
+
+void WebRenderBridgeChild::Connect(CompositableClient* aCompositable,
+ ImageContainer* aImageContainer) {
+ MOZ_ASSERT(!mDestroyed);
+ MOZ_ASSERT(aCompositable);
+
+ CompositableHandle handle = CompositableHandle::GetNext();
+ mCompositables.InsertOrUpdate(uint64_t(handle), aCompositable);
+
+ aCompositable->InitIPDL(handle);
+ SendNewCompositable(handle, aCompositable->GetTextureInfo());
+}
+
+bool WebRenderBridgeChild::AddOpDestroy(const OpDestroy& aOp) {
+ if (!mIsInTransaction) {
+ return false;
+ }
+
+ mDestroyedActors.AppendElement(aOp);
+ return true;
+}
+
+void WebRenderBridgeChild::ReleaseCompositable(
+ const CompositableHandle& aHandle) {
+ if (!IPCOpen()) {
+ // This can happen if the IPC connection was torn down, because, e.g.
+ // the GPU process died.
+ return;
+ }
+ if (!DestroyInTransaction(aHandle)) {
+ SendReleaseCompositable(aHandle);
+ }
+ mCompositables.Remove(aHandle.Value());
+}
+
+bool WebRenderBridgeChild::DestroyInTransaction(PTextureChild* aTexture) {
+ return AddOpDestroy(OpDestroy(WrapNotNull(aTexture)));
+}
+
+bool WebRenderBridgeChild::DestroyInTransaction(
+ const CompositableHandle& aHandle) {
+ return AddOpDestroy(OpDestroy(aHandle));
+}
+
+void WebRenderBridgeChild::RemoveTextureFromCompositable(
+ CompositableClient* aCompositable, TextureClient* aTexture) {
+ MOZ_ASSERT(aCompositable);
+ MOZ_ASSERT(aTexture);
+ MOZ_ASSERT(aTexture->GetIPDLActor());
+ MOZ_RELEASE_ASSERT(aTexture->GetIPDLActor()->GetIPCChannel() ==
+ GetIPCChannel());
+ if (!aCompositable->IsConnected() || !aTexture->GetIPDLActor()) {
+ // We don't have an actor anymore, don't try to use it!
+ return;
+ }
+
+ AddWebRenderParentCommand(CompositableOperation(
+ aCompositable->GetIPCHandle(),
+ OpRemoveTexture(WrapNotNull(aTexture->GetIPDLActor()))));
+}
+
+void WebRenderBridgeChild::UseTextures(
+ CompositableClient* aCompositable,
+ const nsTArray<TimedTextureClient>& aTextures) {
+ MOZ_ASSERT(aCompositable);
+
+ if (!aCompositable->IsConnected()) {
+ return;
+ }
+
+ AutoTArray<TimedTexture, 4> textures;
+
+ for (auto& t : aTextures) {
+ MOZ_ASSERT(t.mTextureClient);
+ MOZ_ASSERT(t.mTextureClient->GetIPDLActor());
+ MOZ_RELEASE_ASSERT(t.mTextureClient->GetIPDLActor()->GetIPCChannel() ==
+ GetIPCChannel());
+ bool readLocked = t.mTextureClient->OnForwardedToHost();
+
+ textures.AppendElement(TimedTexture(
+ WrapNotNull(t.mTextureClient->GetIPDLActor()), t.mTimeStamp,
+ t.mPictureRect, t.mFrameID, t.mProducerID, readLocked));
+ GetCompositorBridgeChild()->HoldUntilCompositableRefReleasedIfNecessary(
+ t.mTextureClient);
+ }
+ AddWebRenderParentCommand(CompositableOperation(aCompositable->GetIPCHandle(),
+ OpUseTexture(textures)));
+}
+
+void WebRenderBridgeChild::UseRemoteTexture(
+ CompositableClient* aCompositable, const RemoteTextureId aTextureId,
+ const RemoteTextureOwnerId aOwnerId, const gfx::IntSize aSize,
+ const TextureFlags aFlags, const RefPtr<FwdTransactionTracker>& aTracker) {
+ AddWebRenderParentCommand(CompositableOperation(
+ aCompositable->GetIPCHandle(),
+ OpUseRemoteTexture(aTextureId, aOwnerId, aSize, aFlags)));
+ TrackFwdTransaction(aTracker);
+}
+
+FwdTransactionCounter& WebRenderBridgeChild::GetFwdTransactionCounter() {
+ return GetCompositorBridgeChild()->GetFwdTransactionCounter();
+}
+
+bool WebRenderBridgeChild::InForwarderThread() { return NS_IsMainThread(); }
+
+mozilla::ipc::IPCResult WebRenderBridgeChild::RecvWrUpdated(
+ const wr::IdNamespace& aNewIdNamespace,
+ const TextureFactoryIdentifier& textureFactoryIdentifier) {
+ if (mManager) {
+ mManager->WrUpdated();
+ }
+ IdentifyTextureHost(textureFactoryIdentifier);
+ // Update mIdNamespace to identify obsolete keys and messages by
+ // WebRenderBridgeParent. Since usage of invalid keys could cause crash in
+ // webrender.
+ mIdNamespace = aNewIdNamespace;
+ // Just clear FontInstaceKeys/FontKeys, they are removed during WebRenderAPI
+ // destruction.
+ mFontInstanceKeys.Clear();
+ mFontKeys.Clear();
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeChild::RecvWrReleasedImages(
+ nsTArray<wr::ExternalImageKeyPair>&& aPairs) {
+ if (mManager) {
+ mManager->WrReleasedImages(aPairs);
+ }
+ return IPC_OK();
+}
+
+void WebRenderBridgeChild::BeginClearCachedResources() {
+ mSentDisplayList = false;
+ mIsInClearCachedResources = true;
+ // Clear display list and animtaions at parent side before clearing cached
+ // resources on client side. It prevents to clear resources before clearing
+ // display list at parent side.
+ SendClearCachedResources();
+}
+
+void WebRenderBridgeChild::EndClearCachedResources() {
+ if (!IPCOpen()) {
+ mIsInClearCachedResources = false;
+ return;
+ }
+ ProcessWebRenderParentCommands();
+ mIsInClearCachedResources = false;
+}
+
+void WebRenderBridgeChild::SetWebRenderLayerManager(
+ WebRenderLayerManager* aManager) {
+ MOZ_ASSERT(aManager && !mManager);
+ MOZ_ASSERT(NS_IsMainThread() || !XRE_IsContentProcess());
+ mManager = aManager;
+}
+
+ipc::IShmemAllocator* WebRenderBridgeChild::GetShmemAllocator() {
+ if (!IPCOpen()) {
+ return nullptr;
+ }
+ return static_cast<CompositorBridgeChild*>(Manager());
+}
+
+RefPtr<KnowsCompositor> WebRenderBridgeChild::GetForMedia() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Ensure device initialization for video playback unless they are all remote.
+ // The devices are lazily initialized with WebRender to reduce memory usage.
+ if (!PDMFactory::AllDecodersAreRemote()) {
+ gfxPlatform::GetPlatform()->EnsureDevicesInitialized();
+ }
+
+ return MakeAndAddRef<KnowsCompositorMediaProxy>(
+ GetTextureFactoryIdentifier());
+}
+
+bool WebRenderBridgeChild::AllocResourceShmem(size_t aSize,
+ RefCountedShmem& aShm) {
+ // We keep a single shmem around to reuse later if it is reference count has
+ // dropped back to 1 (the reference held by the WebRenderBridgeChild).
+
+ // If the cached shmem exists, has the correct size and isn't held by anything
+ // other than us, recycle it.
+ bool alreadyAllocated = RefCountedShm::IsValid(mResourceShm);
+ if (alreadyAllocated) {
+ if (RefCountedShm::GetSize(mResourceShm) == aSize &&
+ RefCountedShm::GetReferenceCount(mResourceShm) <= 1) {
+ MOZ_ASSERT(RefCountedShm::GetReferenceCount(mResourceShm) == 1);
+ aShm = mResourceShm;
+ return true;
+ }
+ }
+
+ // If there was no cached shmem or we couldn't recycle it, alloc a new one.
+ if (!RefCountedShm::Alloc(this, aSize, aShm)) {
+ return false;
+ }
+
+ // Now that we have a valid shmem, put it in the cache if we don't have one
+ // yet.
+ if (!alreadyAllocated) {
+ mResourceShm = aShm;
+ RefCountedShm::AddRef(aShm);
+ }
+
+ return true;
+}
+
+void WebRenderBridgeChild::DeallocResourceShmem(RefCountedShmem& aShm) {
+ if (!RefCountedShm::IsValid(aShm)) {
+ return;
+ }
+ MOZ_ASSERT(RefCountedShm::GetReferenceCount(aShm) == 0);
+
+ RefCountedShm::Dealloc(this, aShm);
+}
+
+void WebRenderBridgeChild::Capture() { this->SendCapture(); }
+
+void WebRenderBridgeChild::StartCaptureSequence(const nsCString& aPath,
+ uint32_t aFlags) {
+ this->SendStartCaptureSequence(aPath, aFlags);
+}
+
+void WebRenderBridgeChild::StopCaptureSequence() {
+ this->SendStopCaptureSequence();
+}
+
+} // namespace layers
+} // namespace mozilla
diff --git a/gfx/layers/wr/WebRenderBridgeChild.h b/gfx/layers/wr/WebRenderBridgeChild.h
new file mode 100644
index 0000000000..90ce55691e
--- /dev/null
+++ b/gfx/layers/wr/WebRenderBridgeChild.h
@@ -0,0 +1,261 @@
+/* -*- 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/. */
+
+#ifndef mozilla_layers_WebRenderBridgeChild_h
+#define mozilla_layers_WebRenderBridgeChild_h
+
+#include "mozilla/layers/CompositableForwarder.h"
+#include "mozilla/layers/PWebRenderBridgeChild.h"
+
+namespace mozilla {
+
+namespace widget {
+class CompositorWidget;
+}
+
+namespace wr {
+class DisplayListBuilder;
+class ResourceUpdateQueue;
+class IpcResourceUpdateQueue;
+} // namespace wr
+
+namespace layers {
+
+class CompositableClient;
+class CompositorBridgeChild;
+class StackingContextHelper;
+class TextureForwarder;
+class WebRenderLayerManager;
+
+template <class T>
+class ThreadSafeWeakPtrHashKey : public PLDHashEntryHdr {
+ public:
+ typedef RefPtr<T> KeyType;
+ typedef const T* KeyTypePointer;
+
+ explicit ThreadSafeWeakPtrHashKey(KeyTypePointer aKey)
+ : mKey(do_AddRef(const_cast<T*>(aKey))) {}
+
+ KeyType GetKey() const { return do_AddRef(mKey); }
+ bool KeyEquals(KeyTypePointer aKey) const { return mKey == aKey; }
+
+ static KeyTypePointer KeyToPointer(const KeyType& aKey) { return aKey.get(); }
+ static PLDHashNumber HashKey(KeyTypePointer aKey) {
+ return NS_PTR_TO_UINT32(aKey) >> 2;
+ }
+ enum { ALLOW_MEMMOVE = true };
+
+ private:
+ ThreadSafeWeakPtr<T> mKey;
+};
+
+typedef ThreadSafeWeakPtrHashKey<gfx::UnscaledFont> UnscaledFontHashKey;
+typedef ThreadSafeWeakPtrHashKey<gfx::ScaledFont> ScaledFontHashKey;
+
+class WebRenderBridgeChild final : public PWebRenderBridgeChild,
+ public CompositableForwarder {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WebRenderBridgeChild, override)
+
+ friend class PWebRenderBridgeChild;
+
+ public:
+ explicit WebRenderBridgeChild(const wr::PipelineId& aPipelineId);
+
+ void AddWebRenderParentCommand(const WebRenderParentCommand& aCmd);
+ bool HasWebRenderParentCommands() { return !mParentCommands.IsEmpty(); }
+
+ void UpdateResources(wr::IpcResourceUpdateQueue& aResources);
+ void BeginTransaction();
+ bool EndTransaction(DisplayListData&& aDisplayListData,
+ TransactionId aTransactionId, bool aContainsSVGroup,
+ const mozilla::VsyncId& aVsyncId,
+ const mozilla::TimeStamp& aVsyncStartTime,
+ const mozilla::TimeStamp& aRefreshStartTime,
+ const mozilla::TimeStamp& aTxnStartTime,
+ const nsCString& aTxtURL);
+ void EndEmptyTransaction(const FocusTarget& aFocusTarget,
+ Maybe<TransactionData>&& aTransactionData,
+ TransactionId aTransactionId,
+ const mozilla::VsyncId& aVsyncId,
+ const mozilla::TimeStamp& aVsyncStartTime,
+ const mozilla::TimeStamp& aRefreshStartTime,
+ const mozilla::TimeStamp& aTxnStartTime,
+ const nsCString& aTxtURL);
+ void ProcessWebRenderParentCommands();
+
+ CompositorBridgeChild* GetCompositorBridgeChild();
+
+ wr::PipelineId GetPipeline() { return mPipelineId; }
+
+ // KnowsCompositor
+ TextureForwarder* GetTextureForwarder() override;
+ LayersIPCActor* GetLayersIPCActor() override;
+ void SyncWithCompositor(
+ const Maybe<uint64_t>& aWindowID = Nothing()) override;
+
+ void AddPipelineIdForCompositable(const wr::PipelineId& aPipelineId,
+ const CompositableHandle& aHandle,
+ CompositableHandleOwner aOwner);
+ void RemovePipelineIdForCompositable(const wr::PipelineId& aPipelineId);
+
+ /// Release TextureClient that is bounded to ImageKey.
+ /// It is used for recycling TextureClient.
+ void ReleaseTextureOfImage(const wr::ImageKey& aKey);
+
+ /**
+ * Clean this up, finishing with SendShutDown() which will cause __delete__
+ * to be sent from the parent side.
+ */
+ void Destroy(bool aIsSync);
+ bool IPCOpen() const { return mIPCOpen && !mDestroyed; }
+ bool GetSentDisplayList() const { return mSentDisplayList; }
+ bool IsDestroyed() const { return mDestroyed; }
+
+ uint32_t GetNextResourceId() { return ++mResourceId; }
+ wr::IdNamespace GetNamespace() { return mIdNamespace; }
+ void SetNamespace(wr::IdNamespace aIdNamespace) {
+ mIdNamespace = aIdNamespace;
+ }
+
+ bool MatchesNamespace(const wr::ImageKey& aImageKey) const {
+ return aImageKey.mNamespace == mIdNamespace;
+ }
+
+ bool MatchesNamespace(const wr::BlobImageKey& aBlobKey) const {
+ return MatchesNamespace(aBlobKey._0);
+ }
+
+ wr::FontKey GetNextFontKey() {
+ return wr::FontKey{GetNamespace(), GetNextResourceId()};
+ }
+
+ wr::FontInstanceKey GetNextFontInstanceKey() {
+ return wr::FontInstanceKey{GetNamespace(), GetNextResourceId()};
+ }
+
+ wr::WrImageKey GetNextImageKey() {
+ return wr::WrImageKey{GetNamespace(), GetNextResourceId()};
+ }
+
+ void PushGlyphs(wr::DisplayListBuilder& aBuilder,
+ wr::IpcResourceUpdateQueue& aResources,
+ Range<const wr::GlyphInstance> aGlyphs,
+ gfx::ScaledFont* aFont, const wr::ColorF& aColor,
+ const StackingContextHelper& aSc,
+ const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
+ bool aBackfaceVisible,
+ const wr::GlyphOptions* aGlyphOptions = nullptr);
+
+ Maybe<wr::FontInstanceKey> GetFontKeyForScaledFont(
+ gfx::ScaledFont* aScaledFont, wr::IpcResourceUpdateQueue& aResources);
+ Maybe<wr::FontKey> GetFontKeyForUnscaledFont(
+ gfx::UnscaledFont* aUnscaledFont, wr::IpcResourceUpdateQueue& aResources);
+ void RemoveExpiredFontKeys(wr::IpcResourceUpdateQueue& aResources);
+
+ void BeginClearCachedResources();
+ void EndClearCachedResources();
+
+ void SetWebRenderLayerManager(WebRenderLayerManager* aManager);
+
+ mozilla::ipc::IShmemAllocator* GetShmemAllocator();
+
+ bool IsThreadSafe() const override { return false; }
+
+ RefPtr<KnowsCompositor> GetForMedia() override;
+
+ /// Alloc a specific type of shmem that is intended for use in
+ /// IpcResourceUpdateQueue only, and cache at most one of them,
+ /// when called multiple times.
+ ///
+ /// Do not use this for anything else.
+ bool AllocResourceShmem(size_t aSize, RefCountedShmem& aShm);
+ /// Dealloc shared memory that was allocated with AllocResourceShmem.
+ ///
+ /// Do not use this for anything else.
+ void DeallocResourceShmem(RefCountedShmem& aShm);
+
+ void Capture();
+ void StartCaptureSequence(const nsCString& path, uint32_t aFlags);
+ void StopCaptureSequence();
+
+ private:
+ friend class CompositorBridgeChild;
+
+ ~WebRenderBridgeChild();
+
+ wr::ExternalImageId GetNextExternalImageId();
+
+ // CompositableForwarder
+ void Connect(CompositableClient* aCompositable,
+ ImageContainer* aImageContainer = nullptr) override;
+ void ReleaseCompositable(const CompositableHandle& aHandle) override;
+ bool DestroyInTransaction(PTextureChild* aTexture) override;
+ bool DestroyInTransaction(const CompositableHandle& aHandle);
+ void RemoveTextureFromCompositable(CompositableClient* aCompositable,
+ TextureClient* aTexture) override;
+ void UseTextures(CompositableClient* aCompositable,
+ const nsTArray<TimedTextureClient>& aTextures) override;
+ void UseRemoteTexture(CompositableClient* aCompositable,
+ const RemoteTextureId aTextureId,
+ const RemoteTextureOwnerId aOwnerId,
+ const gfx::IntSize aSize, const TextureFlags aFlags,
+ const RefPtr<FwdTransactionTracker>& aTracker) override;
+ FwdTransactionCounter& GetFwdTransactionCounter() override;
+
+ bool InForwarderThread() override;
+
+ void ActorDestroy(ActorDestroyReason why) override;
+
+ void DoDestroy();
+
+ mozilla::ipc::IPCResult RecvWrUpdated(
+ const wr::IdNamespace& aNewIdNamespace,
+ const TextureFactoryIdentifier& textureFactoryIdentifier);
+ mozilla::ipc::IPCResult RecvWrReleasedImages(
+ nsTArray<wr::ExternalImageKeyPair>&& aPairs);
+
+ void AddIPDLReference() {
+ MOZ_ASSERT(mIPCOpen == false);
+ mIPCOpen = true;
+ AddRef();
+ }
+ void ReleaseIPDLReference() {
+ MOZ_ASSERT(mIPCOpen == true);
+ mIPCOpen = false;
+ Release();
+ }
+
+ bool AddOpDestroy(const OpDestroy& aOp);
+
+ nsTArray<OpDestroy> mDestroyedActors;
+ nsTArray<WebRenderParentCommand> mParentCommands;
+ nsTHashMap<nsUint64HashKey, CompositableClient*> mCompositables;
+ bool mIsInTransaction;
+ bool mIsInClearCachedResources;
+ wr::IdNamespace mIdNamespace;
+ uint32_t mResourceId;
+ wr::PipelineId mPipelineId;
+ WebRenderLayerManager* mManager;
+
+ bool mIPCOpen;
+ bool mDestroyed;
+ // True iff we have called SendSetDisplayList and haven't called
+ // SendClearCachedResources since that call.
+ bool mSentDisplayList;
+
+ uint32_t mFontKeysDeleted;
+ nsTHashMap<UnscaledFontHashKey, wr::FontKey> mFontKeys;
+
+ uint32_t mFontInstanceKeysDeleted;
+ nsTHashMap<ScaledFontHashKey, wr::FontInstanceKey> mFontInstanceKeys;
+
+ RefCountedShmem mResourceShm;
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif // mozilla_layers_WebRenderBridgeChild_h
diff --git a/gfx/layers/wr/WebRenderBridgeParent.cpp b/gfx/layers/wr/WebRenderBridgeParent.cpp
new file mode 100644
index 0000000000..789476cb6b
--- /dev/null
+++ b/gfx/layers/wr/WebRenderBridgeParent.cpp
@@ -0,0 +1,2886 @@
+/* -*- 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/WebRenderBridgeParent.h"
+
+#include "CompositableHost.h"
+#include "gfxEnv.h"
+#include "gfxPlatform.h"
+#include "gfxOTSUtils.h"
+#include "GeckoProfiler.h"
+#include "GLContext.h"
+#include "GLContextProvider.h"
+#include "GLLibraryLoader.h"
+#include "nsExceptionHandler.h"
+#include "mozilla/Range.h"
+#include "mozilla/EnumeratedRange.h"
+#include "mozilla/StaticPrefs_gfx.h"
+#include "mozilla/StaticPrefs_webgl.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/gfx/gfxVars.h"
+#include "mozilla/gfx/GPUParent.h"
+#include "mozilla/glean/GleanMetrics.h"
+#include "mozilla/layers/AnimationHelper.h"
+#include "mozilla/layers/APZSampler.h"
+#include "mozilla/layers/APZUpdater.h"
+#include "mozilla/layers/Compositor.h"
+#include "mozilla/layers/CompositorBridgeParent.h"
+#include "mozilla/layers/CompositorAnimationStorage.h"
+#include "mozilla/layers/CompositorThread.h"
+#include "mozilla/layers/CompositorVsyncScheduler.h"
+#include "mozilla/layers/ImageBridgeParent.h"
+#include "mozilla/layers/ImageDataSerializer.h"
+#include "mozilla/layers/IpcResourceUpdateQueue.h"
+#include "mozilla/layers/OMTASampler.h"
+#include "mozilla/layers/RemoteTextureMap.h"
+#include "mozilla/layers/SharedSurfacesParent.h"
+#include "mozilla/layers/TextureHost.h"
+#include "mozilla/layers/AsyncImagePipelineManager.h"
+#include "mozilla/layers/UiCompositorControllerParent.h"
+#include "mozilla/layers/WebRenderImageHost.h"
+#include "mozilla/layers/WebRenderTextureHost.h"
+#include "mozilla/ProfilerMarkerTypes.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Unused.h"
+#include "mozilla/webrender/RenderTextureHostSWGL.h"
+#include "mozilla/webrender/RenderThread.h"
+#include "mozilla/widget/CompositorWidget.h"
+
+#ifdef XP_WIN
+# include "mozilla/gfx/DeviceManagerDx.h"
+# include "mozilla/widget/WinCompositorWidget.h"
+#endif
+#if defined(MOZ_WIDGET_GTK)
+# include "mozilla/widget/GtkCompositorWidget.h"
+#endif
+
+bool is_in_main_thread() { return NS_IsMainThread(); }
+
+bool is_in_compositor_thread() {
+ return mozilla::layers::CompositorThreadHolder::IsInCompositorThread();
+}
+
+bool is_in_render_thread() {
+ return mozilla::wr::RenderThread::IsInRenderThread();
+}
+
+bool gecko_profiler_thread_is_being_profiled() {
+ return profiler_thread_is_being_profiled(ThreadProfilingFeatures::Any);
+}
+
+bool is_glcontext_gles(void* const glcontext_ptr) {
+ MOZ_RELEASE_ASSERT(glcontext_ptr);
+ return reinterpret_cast<mozilla::gl::GLContext*>(glcontext_ptr)->IsGLES();
+}
+
+bool is_glcontext_angle(void* glcontext_ptr) {
+ MOZ_ASSERT(glcontext_ptr);
+
+ mozilla::gl::GLContext* glcontext =
+ reinterpret_cast<mozilla::gl::GLContext*>(glcontext_ptr);
+ if (!glcontext) {
+ return false;
+ }
+ return glcontext->IsANGLE();
+}
+
+const char* gfx_wr_resource_path_override() {
+ return gfxPlatform::WebRenderResourcePathOverride();
+}
+
+bool gfx_wr_use_optimized_shaders() {
+ return mozilla::gfx::gfxVars::UseWebRenderOptimizedShaders();
+}
+
+void gfx_critical_note(const char* msg) { gfxCriticalNote << msg; }
+
+void gfx_critical_error(const char* msg) { gfxCriticalError() << msg; }
+
+void gecko_printf_stderr_output(const char* msg) { printf_stderr("%s\n", msg); }
+
+void* get_proc_address_from_glcontext(void* glcontext_ptr,
+ const char* procname) {
+ mozilla::gl::GLContext* glcontext =
+ reinterpret_cast<mozilla::gl::GLContext*>(glcontext_ptr);
+ MOZ_ASSERT(glcontext);
+ if (!glcontext) {
+ return nullptr;
+ }
+ const auto& loader = glcontext->GetSymbolLoader();
+ MOZ_ASSERT(loader);
+
+ const auto ret = loader->GetProcAddress(procname);
+ return reinterpret_cast<void*>(ret);
+}
+
+static CrashReporter::Annotation FromWrCrashAnnotation(
+ mozilla::wr::CrashAnnotation aAnnotation) {
+ switch (aAnnotation) {
+ case mozilla::wr::CrashAnnotation::CompileShader:
+ return CrashReporter::Annotation::GraphicsCompileShader;
+ case mozilla::wr::CrashAnnotation::DrawShader:
+ return CrashReporter::Annotation::GraphicsDrawShader;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unhandled annotation!");
+ return CrashReporter::Annotation::Count;
+ }
+}
+
+extern "C" {
+
+void gfx_wr_set_crash_annotation(mozilla::wr::CrashAnnotation aAnnotation,
+ const char* aValue) {
+ MOZ_ASSERT(aValue);
+
+ auto annotation = FromWrCrashAnnotation(aAnnotation);
+ if (annotation == CrashReporter::Annotation::Count) {
+ return;
+ }
+
+ CrashReporter::AnnotateCrashReport(annotation, nsDependentCString(aValue));
+}
+
+void gfx_wr_clear_crash_annotation(mozilla::wr::CrashAnnotation aAnnotation) {
+ auto annotation = FromWrCrashAnnotation(aAnnotation);
+ if (annotation == CrashReporter::Annotation::Count) {
+ return;
+ }
+
+ CrashReporter::RemoveCrashReportAnnotation(annotation);
+}
+}
+
+namespace mozilla::layers {
+
+using namespace mozilla::gfx;
+
+LazyLogModule gWebRenderBridgeParentLog("WebRenderBridgeParent");
+#define LOG(...) \
+ MOZ_LOG(gWebRenderBridgeParentLog, LogLevel::Debug, (__VA_ARGS__))
+
+class ScheduleObserveLayersUpdate : public wr::NotificationHandler {
+ public:
+ ScheduleObserveLayersUpdate(RefPtr<CompositorBridgeParentBase> aBridge,
+ LayersId aLayersId, bool aIsActive)
+ : mBridge(std::move(aBridge)),
+ mLayersId(aLayersId),
+ mIsActive(aIsActive) {}
+
+ void Notify(wr::Checkpoint) override {
+ CompositorThread()->Dispatch(NewRunnableMethod<LayersId, int>(
+ "ObserveLayersUpdate", mBridge,
+ &CompositorBridgeParentBase::ObserveLayersUpdate, mLayersId,
+ mIsActive));
+ }
+
+ protected:
+ RefPtr<CompositorBridgeParentBase> mBridge;
+ LayersId mLayersId;
+ bool mIsActive;
+};
+
+class SceneBuiltNotification : public wr::NotificationHandler {
+ public:
+ SceneBuiltNotification(WebRenderBridgeParent* aParent, wr::Epoch aEpoch,
+ TimeStamp aTxnStartTime)
+ : mParent(aParent), mEpoch(aEpoch), mTxnStartTime(aTxnStartTime) {}
+
+ void Notify(wr::Checkpoint) override {
+ auto startTime = this->mTxnStartTime;
+ RefPtr<WebRenderBridgeParent> parent = mParent;
+ wr::Epoch epoch = mEpoch;
+ CompositorThread()->Dispatch(NS_NewRunnableFunction(
+ "SceneBuiltNotificationRunnable", [parent, epoch, startTime]() {
+ auto endTime = TimeStamp::Now();
+ if (profiler_thread_is_being_profiled_for_markers()) {
+ PROFILER_MARKER("CONTENT_FULL_PAINT_TIME", GRAPHICS,
+ MarkerTiming::Interval(startTime, endTime),
+ ContentBuildMarker);
+ }
+ mozilla::glean::gfx_content::full_paint_time.AccumulateRawDuration(
+ endTime - startTime);
+ parent->NotifySceneBuiltForEpoch(epoch, endTime);
+ }));
+ }
+
+ protected:
+ RefPtr<WebRenderBridgeParent> mParent;
+ wr::Epoch mEpoch;
+ TimeStamp mTxnStartTime;
+};
+
+class WebRenderBridgeParent::ScheduleSharedSurfaceRelease final
+ : public wr::NotificationHandler {
+ public:
+ explicit ScheduleSharedSurfaceRelease(WebRenderBridgeParent* aWrBridge)
+ : mWrBridge(aWrBridge), mSurfaces(20) {}
+
+ ~ScheduleSharedSurfaceRelease() override {
+ if (!mSurfaces.IsEmpty()) {
+ MOZ_ASSERT_UNREACHABLE("Unreleased surfaces!");
+ gfxCriticalNote << "ScheduleSharedSurfaceRelease destroyed non-empty";
+ NotifyInternal(/* aFromCheckpoint */ false);
+ }
+ }
+
+ void Add(const wr::ImageKey& aKey, const wr::ExternalImageId& aId) {
+ mSurfaces.AppendElement(wr::ExternalImageKeyPair{aKey, aId});
+ }
+
+ void Notify(wr::Checkpoint) override {
+ NotifyInternal(/* aFromCheckpoint */ true);
+ }
+
+ private:
+ void NotifyInternal(bool aFromCheckpoint) {
+ CompositorThread()->Dispatch(
+ NewRunnableMethod<nsTArray<wr::ExternalImageKeyPair>, bool>(
+ "ObserveSharedSurfaceRelease", mWrBridge,
+ &WebRenderBridgeParent::ObserveSharedSurfaceRelease,
+ std::move(mSurfaces), aFromCheckpoint));
+ }
+
+ RefPtr<WebRenderBridgeParent> mWrBridge;
+ nsTArray<wr::ExternalImageKeyPair> mSurfaces;
+};
+
+class MOZ_STACK_CLASS AutoWebRenderBridgeParentAsyncMessageSender final {
+ public:
+ explicit AutoWebRenderBridgeParentAsyncMessageSender(
+ WebRenderBridgeParent* aWebRenderBridgeParent,
+ nsTArray<OpDestroy>* aDestroyActors = nullptr)
+ : mWebRenderBridgeParent(aWebRenderBridgeParent),
+ mActorsToDestroy(aDestroyActors) {
+ mWebRenderBridgeParent->SetAboutToSendAsyncMessages();
+ }
+
+ ~AutoWebRenderBridgeParentAsyncMessageSender() {
+ mWebRenderBridgeParent->SendPendingAsyncMessages();
+ if (mActorsToDestroy) {
+ // Destroy the actors after sending the async messages because the latter
+ // may contain references to some actors.
+ for (const auto& op : *mActorsToDestroy) {
+ mWebRenderBridgeParent->DestroyActor(op);
+ }
+ }
+ }
+
+ private:
+ WebRenderBridgeParent* mWebRenderBridgeParent;
+ nsTArray<OpDestroy>* mActorsToDestroy;
+};
+
+WebRenderBridgeParent::WebRenderBridgeParent(
+ CompositorBridgeParentBase* aCompositorBridge,
+ const wr::PipelineId& aPipelineId, widget::CompositorWidget* aWidget,
+ CompositorVsyncScheduler* aScheduler, RefPtr<wr::WebRenderAPI>&& aApi,
+ RefPtr<AsyncImagePipelineManager>&& aImageMgr, TimeDuration aVsyncRate)
+ : mCompositorBridge(aCompositorBridge),
+ mPipelineId(aPipelineId),
+ mWidget(aWidget),
+ mApi(aApi),
+ mAsyncImageManager(aImageMgr),
+ mCompositorScheduler(aScheduler),
+ mVsyncRate(aVsyncRate),
+ mWrEpoch{0},
+ mIdNamespace(aApi->GetNamespace()),
+#if defined(MOZ_WIDGET_ANDROID)
+ mScreenPixelsTarget(nullptr),
+#endif
+ mBlobTileSize(256),
+ mSkippedCompositeReasons(wr::RenderReasons::NONE),
+ mDestroyed(false),
+ mIsFirstPaint(true),
+ mPendingScrollPayloads("WebRenderBridgeParent::mPendingScrollPayloads") {
+ MOZ_ASSERT(mAsyncImageManager);
+ LOG("WebRenderBridgeParent::WebRenderBridgeParent() PipelineId %" PRIx64
+ " Id %" PRIx64 " root %d",
+ wr::AsUint64(mPipelineId), wr::AsUint64(mApi->GetId()),
+ IsRootWebRenderBridgeParent());
+
+ mAsyncImageManager->AddPipeline(mPipelineId, this);
+ if (IsRootWebRenderBridgeParent()) {
+ MOZ_ASSERT(!mCompositorScheduler);
+ mCompositorScheduler = new CompositorVsyncScheduler(this, mWidget);
+ UpdateDebugFlags();
+ UpdateQualitySettings();
+ UpdateProfilerUI();
+ UpdateParameters();
+ // Start with the cached bool parameter bits inverted so that we update them
+ // all.
+ mBoolParameterBits = ~gfxVars::WebRenderBoolParameters();
+ UpdateBoolParameters();
+ }
+ mRemoteTextureTxnScheduler =
+ RemoteTextureTxnScheduler::Create(aCompositorBridge);
+}
+
+WebRenderBridgeParent::WebRenderBridgeParent(const wr::PipelineId& aPipelineId,
+ nsCString&& aError)
+ : mCompositorBridge(nullptr),
+ mPipelineId(aPipelineId),
+ mWrEpoch{0},
+ mIdNamespace{0},
+ mInitError(aError),
+ mDestroyed(true),
+ mIsFirstPaint(false),
+ mPendingScrollPayloads("WebRenderBridgeParent::mPendingScrollPayloads") {
+ LOG("WebRenderBridgeParent::WebRenderBridgeParent() PipelineId %" PRIx64 "",
+ wr::AsUint64(mPipelineId));
+}
+
+WebRenderBridgeParent::~WebRenderBridgeParent() {
+ LOG("WebRenderBridgeParent::WebRenderBridgeParent() PipelineId %" PRIx64 "",
+ wr::AsUint64(mPipelineId));
+}
+
+/* static */
+WebRenderBridgeParent* WebRenderBridgeParent::CreateDestroyed(
+ const wr::PipelineId& aPipelineId, nsCString&& aError) {
+ return new WebRenderBridgeParent(aPipelineId, std::move(aError));
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvEnsureConnected(
+ TextureFactoryIdentifier* aTextureFactoryIdentifier,
+ MaybeIdNamespace* aMaybeIdNamespace, nsCString* aError) {
+ if (mDestroyed) {
+ *aTextureFactoryIdentifier =
+ TextureFactoryIdentifier(LayersBackend::LAYERS_NONE);
+ *aMaybeIdNamespace = Nothing();
+ if (mInitError.IsEmpty()) {
+ // Got destroyed after we initialized but before the handshake finished?
+ aError->AssignLiteral("FEATURE_FAILURE_WEBRENDER_INITIALIZE_RACE");
+ } else {
+ *aError = std::move(mInitError);
+ }
+ return IPC_OK();
+ }
+
+ MOZ_ASSERT(mIdNamespace.mHandle != 0);
+ *aTextureFactoryIdentifier = GetTextureFactoryIdentifier();
+ *aMaybeIdNamespace = Some(mIdNamespace);
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvShutdown() {
+ return HandleShutdown();
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvShutdownSync() {
+ return HandleShutdown();
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::HandleShutdown() {
+ Destroy();
+ IProtocol* mgr = Manager();
+ if (!Send__delete__(this)) {
+ return IPC_FAIL_NO_REASON(mgr);
+ }
+ return IPC_OK();
+}
+
+void WebRenderBridgeParent::Destroy() {
+ if (mDestroyed) {
+ return;
+ }
+ LOG("WebRenderBridgeParent::Destroy() PipelineId %" PRIx64 " Id %" PRIx64
+ " root %d",
+ wr::AsUint64(mPipelineId), wr::AsUint64(mApi->GetId()),
+ IsRootWebRenderBridgeParent());
+
+ mDestroyed = true;
+ if (mRemoteTextureTxnScheduler) {
+ mRemoteTextureTxnScheduler = nullptr;
+ }
+ if (mWebRenderBridgeRef) {
+ // Break mutual reference
+ mWebRenderBridgeRef->Clear();
+ mWebRenderBridgeRef = nullptr;
+ }
+ for (const auto& entry : mCompositables) {
+ entry.second->OnReleased();
+ }
+ mCompositables.clear();
+ ClearResources();
+}
+
+struct WROTSAlloc {
+ wr::Vec<uint8_t> mVec;
+
+ void* Grow(void* aPtr, size_t aLength) {
+ if (aLength > mVec.Capacity()) {
+ mVec.Reserve(aLength - mVec.Capacity());
+ }
+ return mVec.inner.data;
+ }
+ wr::Vec<uint8_t> ShrinkToFit(void* aPtr, size_t aLength) {
+ wr::Vec<uint8_t> result(std::move(mVec));
+ result.inner.length = aLength;
+ return result;
+ }
+ void Free(void* aPtr) {}
+};
+
+static bool ReadRawFont(const OpAddRawFont& aOp, wr::ShmSegmentsReader& aReader,
+ wr::TransactionBuilder& aUpdates) {
+ wr::Vec<uint8_t> sourceBytes;
+ Maybe<Range<uint8_t>> ptr =
+ aReader.GetReadPointerOrCopy(aOp.bytes(), sourceBytes);
+ if (ptr.isNothing()) {
+ gfxCriticalNote << "No read pointer from reader for sanitizing font "
+ << aOp.key().mHandle;
+ return false;
+ }
+ Range<uint8_t>& source = ptr.ref();
+ // Attempt to sanitize the font before passing it along for updating.
+ // Ensure that we're not strict here about font types, since any font that
+ // failed generating a descriptor might end up here as raw font data.
+ size_t lengthHint = gfxOTSContext::GuessSanitizedFontSize(
+ source.begin().get(), source.length(), false);
+ if (!lengthHint) {
+ gfxCriticalNote << "Could not determine font type for sanitizing font "
+ << aOp.key().mHandle;
+ return false;
+ }
+ gfxOTSExpandingMemoryStream<WROTSAlloc> output(lengthHint);
+ gfxOTSContext otsContext;
+ if (!otsContext.Process(&output, source.begin().get(), source.length())) {
+ gfxCriticalNote << "Failed sanitizing font " << aOp.key().mHandle;
+ return false;
+ }
+ wr::Vec<uint8_t> bytes = output.forget();
+
+ aUpdates.AddRawFont(aOp.key(), bytes, aOp.fontIndex());
+ return true;
+}
+
+bool WebRenderBridgeParent::UpdateResources(
+ const nsTArray<OpUpdateResource>& aResourceUpdates,
+ const nsTArray<RefCountedShmem>& aSmallShmems,
+ const nsTArray<ipc::Shmem>& aLargeShmems,
+ wr::TransactionBuilder& aUpdates) {
+ wr::ShmSegmentsReader reader(aSmallShmems, aLargeShmems);
+ UniquePtr<ScheduleSharedSurfaceRelease> scheduleRelease;
+
+ while (GPUParent::MaybeFlushMemory()) {
+ // If the GPU process has memory pressure, preemptively unmap some of our
+ // shared memory images. If we are in the parent process, the expiration
+ // tracker itself will listen for the memory pressure event.
+ if (!SharedSurfacesParent::AgeAndExpireOneGeneration()) {
+ break;
+ }
+ }
+
+ bool success = true;
+ for (const auto& cmd : aResourceUpdates) {
+ switch (cmd.type()) {
+ case OpUpdateResource::TOpAddImage: {
+ const auto& op = cmd.get_OpAddImage();
+ if (!MatchesNamespace(op.key())) {
+ MOZ_ASSERT_UNREACHABLE("Stale image key (add)!");
+ break;
+ }
+
+ wr::Vec<uint8_t> bytes;
+ if (reader.Read(op.bytes(), bytes)) {
+ aUpdates.AddImage(op.key(), op.descriptor(), bytes);
+ } else {
+ gfxCriticalNote << "TOpAddImage failed";
+ success = false;
+ }
+ break;
+ }
+ case OpUpdateResource::TOpUpdateImage: {
+ const auto& op = cmd.get_OpUpdateImage();
+ if (!MatchesNamespace(op.key())) {
+ MOZ_ASSERT_UNREACHABLE("Stale image key (update)!");
+ break;
+ }
+
+ wr::Vec<uint8_t> bytes;
+ if (reader.Read(op.bytes(), bytes)) {
+ aUpdates.UpdateImageBuffer(op.key(), op.descriptor(), bytes);
+ } else {
+ gfxCriticalNote << "TOpUpdateImage failed";
+ success = false;
+ }
+ break;
+ }
+ case OpUpdateResource::TOpAddBlobImage: {
+ const auto& op = cmd.get_OpAddBlobImage();
+ if (!MatchesNamespace(op.key())) {
+ MOZ_ASSERT_UNREACHABLE("Stale blob image key (add)!");
+ break;
+ }
+
+ wr::Vec<uint8_t> bytes;
+ if (reader.Read(op.bytes(), bytes)) {
+ aUpdates.AddBlobImage(op.key(), op.descriptor(), mBlobTileSize, bytes,
+ wr::ToDeviceIntRect(op.visibleRect()));
+ } else {
+ gfxCriticalNote << "TOpAddBlobImage failed";
+ success = false;
+ }
+
+ break;
+ }
+ case OpUpdateResource::TOpUpdateBlobImage: {
+ const auto& op = cmd.get_OpUpdateBlobImage();
+ if (!MatchesNamespace(op.key())) {
+ MOZ_ASSERT_UNREACHABLE("Stale blob image key (update)!");
+ break;
+ }
+
+ wr::Vec<uint8_t> bytes;
+ if (reader.Read(op.bytes(), bytes)) {
+ aUpdates.UpdateBlobImage(op.key(), op.descriptor(), bytes,
+ wr::ToDeviceIntRect(op.visibleRect()),
+ wr::ToLayoutIntRect(op.dirtyRect()));
+ } else {
+ gfxCriticalNote << "TOpUpdateBlobImage failed";
+ success = false;
+ }
+ break;
+ }
+ case OpUpdateResource::TOpSetBlobImageVisibleArea: {
+ const auto& op = cmd.get_OpSetBlobImageVisibleArea();
+ if (!MatchesNamespace(op.key())) {
+ MOZ_ASSERT_UNREACHABLE("Stale blob image key (visible)!");
+ break;
+ }
+ aUpdates.SetBlobImageVisibleArea(op.key(),
+ wr::ToDeviceIntRect(op.area()));
+ break;
+ }
+ case OpUpdateResource::TOpAddSharedExternalImage: {
+ const auto& op = cmd.get_OpAddSharedExternalImage();
+ // gfxCriticalNote is called on error
+ if (!AddSharedExternalImage(op.externalImageId(), op.key(), aUpdates)) {
+ success = false;
+ }
+ break;
+ }
+ case OpUpdateResource::TOpPushExternalImageForTexture: {
+ const auto& op = cmd.get_OpPushExternalImageForTexture();
+ CompositableTextureHostRef texture;
+ texture = TextureHost::AsTextureHost(op.texture().AsParent());
+ // gfxCriticalNote is called on error
+ if (!PushExternalImageForTexture(op.externalImageId(), op.key(),
+ texture, op.isUpdate(), aUpdates)) {
+ success = false;
+ }
+ break;
+ }
+ case OpUpdateResource::TOpUpdateSharedExternalImage: {
+ const auto& op = cmd.get_OpUpdateSharedExternalImage();
+ // gfxCriticalNote is called on error
+ if (!UpdateSharedExternalImage(op.externalImageId(), op.key(),
+ op.dirtyRect(), aUpdates,
+ scheduleRelease)) {
+ success = false;
+ }
+ break;
+ }
+ case OpUpdateResource::TOpAddRawFont: {
+ if (!ReadRawFont(cmd.get_OpAddRawFont(), reader, aUpdates)) {
+ success = false;
+ }
+ break;
+ }
+ case OpUpdateResource::TOpAddFontDescriptor: {
+ const auto& op = cmd.get_OpAddFontDescriptor();
+ if (!MatchesNamespace(op.key())) {
+ MOZ_ASSERT_UNREACHABLE("Stale font key (add descriptor)!");
+ break;
+ }
+
+ wr::Vec<uint8_t> bytes;
+ if (reader.Read(op.bytes(), bytes)) {
+ aUpdates.AddFontDescriptor(op.key(), bytes, op.fontIndex());
+ } else {
+ gfxCriticalNote << "TOpAddFontDescriptor failed";
+ success = false;
+ }
+ break;
+ }
+ case OpUpdateResource::TOpAddFontInstance: {
+ const auto& op = cmd.get_OpAddFontInstance();
+ if (!MatchesNamespace(op.instanceKey()) ||
+ !MatchesNamespace(op.fontKey())) {
+ MOZ_ASSERT_UNREACHABLE("Stale font key (add instance)!");
+ break;
+ }
+
+ wr::Vec<uint8_t> variations;
+ if (reader.Read(op.variations(), variations)) {
+ aUpdates.AddFontInstance(op.instanceKey(), op.fontKey(),
+ op.glyphSize(), op.options().ptrOr(nullptr),
+ op.platformOptions().ptrOr(nullptr),
+ variations);
+ } else {
+ gfxCriticalNote << "TOpAddFontInstance failed";
+ success = false;
+ }
+ break;
+ }
+ case OpUpdateResource::TOpDeleteImage: {
+ const auto& op = cmd.get_OpDeleteImage();
+ if (!MatchesNamespace(op.key())) {
+ // TODO(aosmond): We should also assert here, but the callers are less
+ // careful about checking when cleaning up their old keys. We should
+ // perform an audit on image key usage.
+ break;
+ }
+
+ DeleteImage(op.key(), aUpdates);
+ break;
+ }
+ case OpUpdateResource::TOpDeleteBlobImage: {
+ const auto& op = cmd.get_OpDeleteBlobImage();
+ if (!MatchesNamespace(op.key())) {
+ MOZ_ASSERT_UNREACHABLE("Stale blob image key (delete)!");
+ break;
+ }
+
+ aUpdates.DeleteBlobImage(op.key());
+ break;
+ }
+ case OpUpdateResource::TOpDeleteFont: {
+ const auto& op = cmd.get_OpDeleteFont();
+ if (!MatchesNamespace(op.key())) {
+ MOZ_ASSERT_UNREACHABLE("Stale font key (delete)!");
+ break;
+ }
+
+ aUpdates.DeleteFont(op.key());
+ break;
+ }
+ case OpUpdateResource::TOpDeleteFontInstance: {
+ const auto& op = cmd.get_OpDeleteFontInstance();
+ if (!MatchesNamespace(op.key())) {
+ MOZ_ASSERT_UNREACHABLE("Stale font instance key (delete)!");
+ break;
+ }
+
+ aUpdates.DeleteFontInstance(op.key());
+ break;
+ }
+ case OpUpdateResource::T__None:
+ break;
+ }
+ }
+
+ if (scheduleRelease) {
+ // When software WR is enabled, shared surfaces are read during rendering
+ // rather than copied to the texture cache.
+ wr::Checkpoint when = mApi->GetBackendType() == WebRenderBackend::SOFTWARE
+ ? wr::Checkpoint::FrameRendered
+ : wr::Checkpoint::FrameTexturesUpdated;
+ aUpdates.Notify(when, std::move(scheduleRelease));
+ }
+
+ MOZ_ASSERT(success);
+ return success;
+}
+
+bool WebRenderBridgeParent::AddSharedExternalImage(
+ wr::ExternalImageId aExtId, wr::ImageKey aKey,
+ wr::TransactionBuilder& aResources) {
+ if (!MatchesNamespace(aKey)) {
+ MOZ_ASSERT_UNREACHABLE("Stale shared external image key (add)!");
+ return true;
+ }
+
+ auto key = wr::AsUint64(aKey);
+ auto it = mSharedSurfaceIds.find(key);
+ if (it != mSharedSurfaceIds.end()) {
+ gfxCriticalNote << "Readding known shared surface: " << key;
+ return false;
+ }
+
+ RefPtr<DataSourceSurface> dSurf = SharedSurfacesParent::Acquire(aExtId);
+ if (!dSurf) {
+ gfxCriticalNote
+ << "DataSourceSurface of SharedSurfaces does not exist for extId:"
+ << wr::AsUint64(aExtId);
+ return false;
+ }
+
+ mSharedSurfaceIds.insert(std::make_pair(key, aExtId));
+
+ // Prefer raw buffers, unless our backend requires native textures.
+ IntSize surfaceSize = dSurf->GetSize();
+ TextureHost::NativeTexturePolicy policy =
+ TextureHost::BackendNativeTexturePolicy(mApi->GetBackendType(),
+ surfaceSize);
+ auto imageType =
+ policy == TextureHost::NativeTexturePolicy::REQUIRE
+ ? wr::ExternalImageType::TextureHandle(wr::ImageBufferKind::Texture2D)
+ : wr::ExternalImageType::Buffer();
+ wr::ImageDescriptor descriptor(surfaceSize, dSurf->Stride(),
+ dSurf->GetFormat());
+ aResources.AddExternalImage(aKey, descriptor, aExtId, imageType, 0);
+ return true;
+}
+
+bool WebRenderBridgeParent::PushExternalImageForTexture(
+ wr::ExternalImageId aExtId, wr::ImageKey aKey, TextureHost* aTexture,
+ bool aIsUpdate, wr::TransactionBuilder& aResources) {
+ if (!MatchesNamespace(aKey)) {
+ MOZ_ASSERT_UNREACHABLE("Stale texture external image key!");
+ return true;
+ }
+
+ if (!aTexture) {
+ gfxCriticalNote << "TextureHost does not exist for extId:"
+ << wr::AsUint64(aExtId);
+ return false;
+ }
+
+ auto op = aIsUpdate ? TextureHost::UPDATE_IMAGE : TextureHost::ADD_IMAGE;
+ WebRenderTextureHost* wrTexture = aTexture->AsWebRenderTextureHost();
+ if (wrTexture) {
+ Range<wr::ImageKey> keys(&aKey, 1);
+ wrTexture->PushResourceUpdates(aResources, op, keys,
+ wrTexture->GetExternalImageKey());
+ auto it = mTextureHosts.find(wr::AsUint64(aKey));
+ MOZ_ASSERT((it == mTextureHosts.end() && !aIsUpdate) ||
+ (it != mTextureHosts.end() && aIsUpdate));
+ if (it != mTextureHosts.end()) {
+ // Release Texture if it exists.
+ ReleaseTextureOfImage(aKey);
+ }
+ mTextureHosts.emplace(wr::AsUint64(aKey),
+ CompositableTextureHostRef(aTexture));
+ return true;
+ }
+
+ RefPtr<DataSourceSurface> dSurf = aTexture->GetAsSurface();
+ if (!dSurf) {
+ gfxCriticalNote
+ << "TextureHost does not return DataSourceSurface for extId:"
+ << wr::AsUint64(aExtId);
+ return false;
+ }
+
+ DataSourceSurface::MappedSurface map;
+ if (!dSurf->Map(gfx::DataSourceSurface::MapType::READ, &map)) {
+ gfxCriticalNote << "DataSourceSurface failed to map for Image for extId:"
+ << wr::AsUint64(aExtId);
+ return false;
+ }
+
+ IntSize size = dSurf->GetSize();
+ wr::ImageDescriptor descriptor(size, map.mStride, dSurf->GetFormat());
+ wr::Vec<uint8_t> data;
+ data.PushBytes(Range<uint8_t>(map.mData, size.height * map.mStride));
+
+ if (op == TextureHost::UPDATE_IMAGE) {
+ aResources.UpdateImageBuffer(aKey, descriptor, data);
+ } else {
+ aResources.AddImage(aKey, descriptor, data);
+ }
+
+ dSurf->Unmap();
+
+ return true;
+}
+
+bool WebRenderBridgeParent::UpdateSharedExternalImage(
+ wr::ExternalImageId aExtId, wr::ImageKey aKey,
+ const ImageIntRect& aDirtyRect, wr::TransactionBuilder& aResources,
+ UniquePtr<ScheduleSharedSurfaceRelease>& aScheduleRelease) {
+ if (!MatchesNamespace(aKey)) {
+ MOZ_ASSERT_UNREACHABLE("Stale shared external image key (update)!");
+ return true;
+ }
+
+ auto key = wr::AsUint64(aKey);
+ auto it = mSharedSurfaceIds.find(key);
+ if (it == mSharedSurfaceIds.end()) {
+ gfxCriticalNote << "Updating unknown shared surface: " << key;
+ return false;
+ }
+
+ RefPtr<DataSourceSurface> dSurf;
+ if (it->second == aExtId) {
+ dSurf = SharedSurfacesParent::Get(aExtId);
+ } else {
+ dSurf = SharedSurfacesParent::Acquire(aExtId);
+ }
+
+ if (!dSurf) {
+ gfxCriticalNote << "Shared surface does not exist for extId:"
+ << wr::AsUint64(aExtId);
+ return false;
+ }
+
+ if (!(it->second == aExtId)) {
+ // We already have a mapping for this image key, so ensure we release the
+ // previous external image ID. This can happen when an image is animated,
+ // and it is changing the external image that the animation points to.
+ if (!aScheduleRelease) {
+ aScheduleRelease = MakeUnique<ScheduleSharedSurfaceRelease>(this);
+ }
+ aScheduleRelease->Add(aKey, it->second);
+ it->second = aExtId;
+ }
+
+ // Prefer raw buffers, unless our backend requires native textures.
+ IntSize surfaceSize = dSurf->GetSize();
+ TextureHost::NativeTexturePolicy policy =
+ TextureHost::BackendNativeTexturePolicy(mApi->GetBackendType(),
+ surfaceSize);
+ auto imageType =
+ policy == TextureHost::NativeTexturePolicy::REQUIRE
+ ? wr::ExternalImageType::TextureHandle(wr::ImageBufferKind::Texture2D)
+ : wr::ExternalImageType::Buffer();
+ wr::ImageDescriptor descriptor(surfaceSize, dSurf->Stride(),
+ dSurf->GetFormat());
+ aResources.UpdateExternalImageWithDirtyRect(
+ aKey, descriptor, aExtId, imageType, wr::ToDeviceIntRect(aDirtyRect), 0);
+
+ return true;
+}
+
+void WebRenderBridgeParent::ObserveSharedSurfaceRelease(
+ const nsTArray<wr::ExternalImageKeyPair>& aPairs,
+ const bool& aFromCheckpoint) {
+ if (!mDestroyed) {
+ Unused << SendWrReleasedImages(aPairs);
+ }
+
+ if (!aFromCheckpoint && mAsyncImageManager) {
+ // We failed to receive a checkpoint notification, so we are releasing these
+ // surfaces blind. Let's wait until the next epoch to complete releasing.
+ for (const auto& pair : aPairs) {
+ mAsyncImageManager->HoldExternalImage(mPipelineId, mWrEpoch, pair.id);
+ }
+ return;
+ }
+
+ // We hit the checkpoint, so we know we can safely release the surfaces now.
+ for (const auto& pair : aPairs) {
+ SharedSurfacesParent::Release(pair.id);
+ }
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvUpdateResources(
+ const wr::IdNamespace& aIdNamespace,
+ nsTArray<OpUpdateResource>&& aResourceUpdates,
+ nsTArray<RefCountedShmem>&& aSmallShmems,
+ nsTArray<ipc::Shmem>&& aLargeShmems) {
+ const bool isValidMessage = aIdNamespace == mIdNamespace;
+
+ if (mDestroyed || !isValidMessage) {
+ wr::IpcResourceUpdateQueue::ReleaseShmems(this, aSmallShmems);
+ wr::IpcResourceUpdateQueue::ReleaseShmems(this, aLargeShmems);
+ return IPC_OK();
+ }
+
+ LOG("WebRenderBridgeParent::RecvUpdateResources() PipelineId %" PRIx64
+ " Id %" PRIx64 " root %d",
+ wr::AsUint64(mPipelineId), wr::AsUint64(mApi->GetId()),
+ IsRootWebRenderBridgeParent());
+
+ wr::TransactionBuilder txn(mApi);
+ txn.SetLowPriority(!IsRootWebRenderBridgeParent());
+
+ Unused << GetNextWrEpoch();
+
+ bool success =
+ UpdateResources(aResourceUpdates, aSmallShmems, aLargeShmems, txn);
+ wr::IpcResourceUpdateQueue::ReleaseShmems(this, aSmallShmems);
+ wr::IpcResourceUpdateQueue::ReleaseShmems(this, aLargeShmems);
+
+ // Even when txn.IsResourceUpdatesEmpty() is true, there could be resource
+ // updates. It is handled by WebRenderTextureHostWrapper. In this case
+ // txn.IsRenderedFrameInvalidated() becomes true.
+ if (!txn.IsResourceUpdatesEmpty() || txn.IsRenderedFrameInvalidated()) {
+ // There are resource updates, then we update Epoch of transaction.
+ txn.UpdateEpoch(mPipelineId, mWrEpoch);
+ mAsyncImageManager->SetWillGenerateFrame();
+ ScheduleGenerateFrame(wr::RenderReasons::RESOURCE_UPDATE);
+ } else {
+ // If TransactionBuilder does not have resource updates nor display list,
+ // ScheduleGenerateFrame is not triggered via SceneBuilder and there is no
+ // need to update WrEpoch.
+ // Then we want to rollback WrEpoch. See Bug 1490117.
+ RollbackWrEpoch();
+ }
+
+ mApi->SendTransaction(txn);
+
+ if (!success) {
+ return IPC_FAIL(this, "Invalid WebRender resource data shmem or address.");
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvDeleteCompositorAnimations(
+ nsTArray<uint64_t>&& aIds) {
+ if (mDestroyed) {
+ return IPC_OK();
+ }
+
+ LOG("WebRenderBridgeParent::RecvDeleteCompositorAnimations() PipelineId "
+ "%" PRIx64 " Id %" PRIx64 " root %d",
+ wr::AsUint64(mPipelineId), wr::AsUint64(mApi->GetId()),
+ IsRootWebRenderBridgeParent());
+
+ // Once mWrEpoch has been rendered, we can delete these compositor animations
+ mCompositorAnimationsToDelete.push(
+ CompositorAnimationIdsForEpoch(mWrEpoch, std::move(aIds)));
+ return IPC_OK();
+}
+
+void WebRenderBridgeParent::RemoveEpochDataPriorTo(
+ const wr::Epoch& aRenderedEpoch) {
+ if (RefPtr<OMTASampler> sampler = GetOMTASampler()) {
+ sampler->RemoveEpochDataPriorTo(mCompositorAnimationsToDelete,
+ mActiveAnimations, aRenderedEpoch);
+ }
+}
+
+bool WebRenderBridgeParent::IsRootWebRenderBridgeParent() const {
+ return !!mWidget;
+}
+
+void WebRenderBridgeParent::BeginRecording(const TimeStamp& aRecordingStart) {
+ mApi->BeginRecording(aRecordingStart, mPipelineId);
+}
+
+RefPtr<wr::WebRenderAPI::EndRecordingPromise>
+WebRenderBridgeParent::EndRecording() {
+ return mApi->EndRecording();
+}
+
+void WebRenderBridgeParent::AddPendingScrollPayload(
+ CompositionPayload& aPayload, const VsyncId& aCompositeStartId) {
+ auto pendingScrollPayloads = mPendingScrollPayloads.Lock();
+ nsTArray<CompositionPayload>* payloads =
+ pendingScrollPayloads->GetOrInsertNew(aCompositeStartId.mId);
+
+ payloads->AppendElement(aPayload);
+}
+
+nsTArray<CompositionPayload> WebRenderBridgeParent::TakePendingScrollPayload(
+ const VsyncId& aCompositeStartId) {
+ auto pendingScrollPayloads = mPendingScrollPayloads.Lock();
+ nsTArray<CompositionPayload> payload;
+ if (nsTArray<CompositionPayload>* storedPayload =
+ pendingScrollPayloads->Get(aCompositeStartId.mId)) {
+ payload.AppendElements(std::move(*storedPayload));
+ pendingScrollPayloads->Remove(aCompositeStartId.mId);
+ }
+ return payload;
+}
+
+CompositorBridgeParent* WebRenderBridgeParent::GetRootCompositorBridgeParent()
+ const {
+ if (!mCompositorBridge) {
+ return nullptr;
+ }
+
+ if (IsRootWebRenderBridgeParent()) {
+ // This WebRenderBridgeParent is attached to the root
+ // CompositorBridgeParent.
+ return static_cast<CompositorBridgeParent*>(mCompositorBridge);
+ }
+
+ // Otherwise, this WebRenderBridgeParent is attached to a
+ // ContentCompositorBridgeParent so we have an extra level of
+ // indirection to unravel.
+ CompositorBridgeParent::LayerTreeState* lts =
+ CompositorBridgeParent::GetIndirectShadowTree(GetLayersId());
+ if (!lts) {
+ return nullptr;
+ }
+ return lts->mParent;
+}
+
+RefPtr<WebRenderBridgeParent>
+WebRenderBridgeParent::GetRootWebRenderBridgeParent() const {
+ CompositorBridgeParent* cbp = GetRootCompositorBridgeParent();
+ if (!cbp) {
+ return nullptr;
+ }
+
+ return cbp->GetWebRenderBridgeParent();
+}
+
+void WebRenderBridgeParent::UpdateAPZFocusState(const FocusTarget& aFocus) {
+ CompositorBridgeParent* cbp = GetRootCompositorBridgeParent();
+ if (!cbp) {
+ return;
+ }
+ LayersId rootLayersId = cbp->RootLayerTreeId();
+ if (RefPtr<APZUpdater> apz = cbp->GetAPZUpdater()) {
+ apz->UpdateFocusState(rootLayersId, GetLayersId(), aFocus);
+ }
+}
+
+void WebRenderBridgeParent::UpdateAPZScrollData(const wr::Epoch& aEpoch,
+ WebRenderScrollData&& aData) {
+ CompositorBridgeParent* cbp = GetRootCompositorBridgeParent();
+ if (!cbp) {
+ return;
+ }
+ LayersId rootLayersId = cbp->RootLayerTreeId();
+ if (RefPtr<APZUpdater> apz = cbp->GetAPZUpdater()) {
+ apz->UpdateScrollDataAndTreeState(rootLayersId, GetLayersId(), aEpoch,
+ std::move(aData));
+ }
+}
+
+void WebRenderBridgeParent::UpdateAPZScrollOffsets(
+ ScrollUpdatesMap&& aUpdates, uint32_t aPaintSequenceNumber) {
+ CompositorBridgeParent* cbp = GetRootCompositorBridgeParent();
+ if (!cbp) {
+ return;
+ }
+ LayersId rootLayersId = cbp->RootLayerTreeId();
+ if (RefPtr<APZUpdater> apz = cbp->GetAPZUpdater()) {
+ apz->UpdateScrollOffsets(rootLayersId, GetLayersId(), std::move(aUpdates),
+ aPaintSequenceNumber);
+ }
+}
+
+void WebRenderBridgeParent::SetAPZSampleTime() {
+ CompositorBridgeParent* cbp = GetRootCompositorBridgeParent();
+ if (!cbp) {
+ return;
+ }
+ if (RefPtr<APZSampler> apz = cbp->GetAPZSampler()) {
+ SampleTime animationTime;
+ if (Maybe<TimeStamp> testTime = cbp->GetTestingTimeStamp()) {
+ animationTime = SampleTime::FromTest(*testTime);
+ } else {
+ animationTime = mCompositorScheduler->GetLastComposeTime();
+ }
+ TimeDuration frameInterval = cbp->GetVsyncInterval();
+ // As with the non-webrender codepath in AsyncCompositionManager, we want to
+ // use the timestamp for the next vsync when advancing animations.
+ if (frameInterval != TimeDuration::Forever()) {
+ animationTime = animationTime + frameInterval;
+ }
+ apz->SetSampleTime(animationTime);
+ }
+}
+
+bool WebRenderBridgeParent::SetDisplayList(
+ const LayoutDeviceRect& aRect, ipc::ByteBuf&& aDLItems,
+ ipc::ByteBuf&& aDLCache, ipc::ByteBuf&& aSpatialTreeDL,
+ const wr::BuiltDisplayListDescriptor& aDLDesc,
+ const nsTArray<OpUpdateResource>& aResourceUpdates,
+ const nsTArray<RefCountedShmem>& aSmallShmems,
+ const nsTArray<ipc::Shmem>& aLargeShmems, const TimeStamp& aTxnStartTime,
+ wr::TransactionBuilder& aTxn, wr::Epoch aWrEpoch) {
+ bool success =
+ UpdateResources(aResourceUpdates, aSmallShmems, aLargeShmems, aTxn);
+
+ wr::Vec<uint8_t> dlItems(std::move(aDLItems));
+ wr::Vec<uint8_t> dlCache(std::move(aDLCache));
+ wr::Vec<uint8_t> dlSpatialTreeData(std::move(aSpatialTreeDL));
+
+ if (IsRootWebRenderBridgeParent()) {
+#ifdef MOZ_WIDGET_GTK
+ if (mWidget->AsGTK()) {
+ mWidget->AsGTK()->RemoteLayoutSizeUpdated(aRect);
+ }
+#endif
+ LayoutDeviceIntSize widgetSize = mWidget->GetClientSize();
+ LayoutDeviceIntRect rect =
+ LayoutDeviceIntRect(LayoutDeviceIntPoint(), widgetSize);
+ aTxn.SetDocumentView(rect);
+ }
+ aTxn.SetDisplayList(aWrEpoch, mPipelineId, aDLDesc, dlItems, dlCache,
+ dlSpatialTreeData);
+
+ MaybeNotifyOfLayers(aTxn, true);
+
+ if (!IsRootWebRenderBridgeParent()) {
+ aTxn.Notify(wr::Checkpoint::SceneBuilt, MakeUnique<SceneBuiltNotification>(
+ this, aWrEpoch, aTxnStartTime));
+ }
+
+ mApi->SendTransaction(aTxn);
+
+ // We will schedule generating a frame after the scene
+ // build is done, so we don't need to do it here.
+ return success;
+}
+
+bool WebRenderBridgeParent::ProcessDisplayListData(
+ DisplayListData& aDisplayList, wr::Epoch aWrEpoch,
+ const TimeStamp& aTxnStartTime, bool aValidTransaction) {
+ wr::TransactionBuilder txn(mApi);
+ Maybe<wr::AutoTransactionSender> sender;
+
+ if (aDisplayList.mScrollData && !aDisplayList.mScrollData->Validate()) {
+ // If the scroll data is invalid, the entire transaction needs to be dropped
+ // because the scroll data and the display list cross-reference each other.
+ MOZ_ASSERT(
+ false,
+ "Content sent malformed scroll data (or validation check has a bug)");
+ aValidTransaction = false;
+ }
+
+ if (!aValidTransaction) {
+ return true;
+ }
+
+ MOZ_ASSERT(aDisplayList.mIdNamespace == mIdNamespace);
+
+ // Note that this needs to happen before the display list transaction is
+ // sent to WebRender, so that the UpdateHitTestingTree call is guaranteed to
+ // be in the updater queue at the time that the scene swap completes.
+ if (aDisplayList.mScrollData) {
+ UpdateAPZScrollData(aWrEpoch, std::move(aDisplayList.mScrollData.ref()));
+ }
+
+ txn.SetLowPriority(!IsRootWebRenderBridgeParent());
+ sender.emplace(mApi, &txn);
+ bool success = true;
+
+ success =
+ ProcessWebRenderParentCommands(aDisplayList.mCommands, txn) && success;
+
+ if (aDisplayList.mDLItems && aDisplayList.mDLCache &&
+ aDisplayList.mDLSpatialTree) {
+ success =
+ SetDisplayList(
+ aDisplayList.mRect, std::move(aDisplayList.mDLItems.ref()),
+ std::move(aDisplayList.mDLCache.ref()),
+ std::move(aDisplayList.mDLSpatialTree.ref()), aDisplayList.mDLDesc,
+ aDisplayList.mResourceUpdates, aDisplayList.mSmallShmems,
+ aDisplayList.mLargeShmems, aTxnStartTime, txn, aWrEpoch) &&
+ success;
+ }
+ return success;
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvSetDisplayList(
+ DisplayListData&& aDisplayList, nsTArray<OpDestroy>&& aToDestroy,
+ const uint64_t& aFwdTransactionId, const TransactionId& aTransactionId,
+ const bool& aContainsSVGGroup, const VsyncId& aVsyncId,
+ const TimeStamp& aVsyncStartTime, const TimeStamp& aRefreshStartTime,
+ const TimeStamp& aTxnStartTime, const nsACString& aTxnURL,
+ const TimeStamp& aFwdTime, nsTArray<CompositionPayload>&& aPayloads) {
+ if (mDestroyed) {
+ for (const auto& op : aToDestroy) {
+ DestroyActor(op);
+ }
+ wr::IpcResourceUpdateQueue::ReleaseShmems(this, aDisplayList.mSmallShmems);
+ wr::IpcResourceUpdateQueue::ReleaseShmems(this, aDisplayList.mLargeShmems);
+ return IPC_OK();
+ }
+
+ LOG("WebRenderBridgeParent::RecvSetDisplayList() PipelineId %" PRIx64
+ " Id %" PRIx64 " root %d",
+ wr::AsUint64(mPipelineId), wr::AsUint64(mApi->GetId()),
+ IsRootWebRenderBridgeParent());
+
+ if (!IsRootWebRenderBridgeParent()) {
+ CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::URL, aTxnURL);
+ }
+
+ CompositorBridgeParent* cbp = GetRootCompositorBridgeParent();
+ uint64_t innerWindowId = cbp ? cbp->GetInnerWindowId() : 0;
+ AUTO_PROFILER_TRACING_MARKER_INNERWINDOWID("Paint", "SetDisplayList",
+ GRAPHICS, innerWindowId);
+ UpdateFwdTransactionId(aFwdTransactionId);
+
+ // This ensures that destroy operations are always processed. It is not safe
+ // to early-return from RecvDPEnd without doing so.
+ AutoWebRenderBridgeParentAsyncMessageSender autoAsyncMessageSender(
+ this, &aToDestroy);
+
+ wr::Epoch wrEpoch = GetNextWrEpoch();
+
+ mReceivedDisplayList = true;
+
+ if (aDisplayList.mScrollData && aDisplayList.mScrollData->IsFirstPaint()) {
+ mIsFirstPaint = true;
+ }
+
+ bool validTransaction = aDisplayList.mIdNamespace == mIdNamespace;
+ bool success = ProcessDisplayListData(aDisplayList, wrEpoch, aTxnStartTime,
+ validTransaction);
+
+ if (!IsRootWebRenderBridgeParent()) {
+ aPayloads.AppendElement(
+ CompositionPayload{CompositionPayloadType::eContentPaint, aFwdTime});
+ }
+
+ HoldPendingTransactionId(wrEpoch, aTransactionId, aContainsSVGGroup, aVsyncId,
+ aVsyncStartTime, aRefreshStartTime, aTxnStartTime,
+ aTxnURL, aFwdTime, mIsFirstPaint,
+ std::move(aPayloads));
+ mIsFirstPaint = false;
+
+ if (!validTransaction) {
+ // Pretend we composited since someone is wating for this event,
+ // though DisplayList was not pushed to webrender.
+ if (CompositorBridgeParent* cbp = GetRootCompositorBridgeParent()) {
+ TimeStamp now = TimeStamp::Now();
+ cbp->NotifyPipelineRendered(mPipelineId, wrEpoch, VsyncId(), now, now,
+ now);
+ }
+ }
+
+ wr::IpcResourceUpdateQueue::ReleaseShmems(this, aDisplayList.mSmallShmems);
+ wr::IpcResourceUpdateQueue::ReleaseShmems(this, aDisplayList.mLargeShmems);
+
+ if (mRemoteTextureTxnScheduler) {
+ mRemoteTextureTxnScheduler->NotifyTxn(aFwdTransactionId);
+ }
+
+ if (!success) {
+ return IPC_FAIL(this, "Failed to process DisplayListData.");
+ }
+
+ return IPC_OK();
+}
+
+bool WebRenderBridgeParent::ProcessEmptyTransactionUpdates(
+ TransactionData& aData, bool* aScheduleComposite) {
+ *aScheduleComposite = false;
+ wr::TransactionBuilder txn(mApi);
+ txn.SetLowPriority(!IsRootWebRenderBridgeParent());
+
+ if (!aData.mScrollUpdates.IsEmpty()) {
+ UpdateAPZScrollOffsets(std::move(aData.mScrollUpdates),
+ aData.mPaintSequenceNumber);
+ }
+
+ // Update WrEpoch for UpdateResources() and ProcessWebRenderParentCommands().
+ // WrEpoch is used to manage ExternalImages lifetimes in
+ // AsyncImagePipelineManager.
+ Unused << GetNextWrEpoch();
+
+ const bool validTransaction = aData.mIdNamespace == mIdNamespace;
+ bool success = true;
+
+ if (validTransaction) {
+ success = UpdateResources(aData.mResourceUpdates, aData.mSmallShmems,
+ aData.mLargeShmems, txn);
+ if (!aData.mCommands.IsEmpty()) {
+ success = ProcessWebRenderParentCommands(aData.mCommands, txn) && success;
+ }
+ }
+
+ MaybeNotifyOfLayers(txn, true);
+
+ // Even when txn.IsResourceUpdatesEmpty() is true, there could be resource
+ // updates. It is handled by WebRenderTextureHostWrapper. In this case
+ // txn.IsRenderedFrameInvalidated() becomes true.
+ if (!txn.IsResourceUpdatesEmpty() || txn.IsRenderedFrameInvalidated()) {
+ // There are resource updates, then we update Epoch of transaction.
+ txn.UpdateEpoch(mPipelineId, mWrEpoch);
+ *aScheduleComposite = true;
+ } else {
+ // If TransactionBuilder does not have resource updates nor display list,
+ // ScheduleGenerateFrame is not triggered via SceneBuilder and there is no
+ // need to update WrEpoch.
+ // Then we want to rollback WrEpoch. See Bug 1490117.
+ RollbackWrEpoch();
+ }
+
+ if (!txn.IsEmpty()) {
+ mApi->SendTransaction(txn);
+ }
+
+ if (*aScheduleComposite) {
+ mAsyncImageManager->SetWillGenerateFrame();
+ }
+
+ return success;
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvEmptyTransaction(
+ const FocusTarget& aFocusTarget, Maybe<TransactionData>&& aTransactionData,
+ nsTArray<OpDestroy>&& aToDestroy, const uint64_t& aFwdTransactionId,
+ const TransactionId& aTransactionId, const VsyncId& aVsyncId,
+ const TimeStamp& aVsyncStartTime, const TimeStamp& aRefreshStartTime,
+ const TimeStamp& aTxnStartTime, const nsACString& aTxnURL,
+ const TimeStamp& aFwdTime, nsTArray<CompositionPayload>&& aPayloads) {
+ if (mDestroyed) {
+ for (const auto& op : aToDestroy) {
+ DestroyActor(op);
+ }
+ if (aTransactionData) {
+ wr::IpcResourceUpdateQueue::ReleaseShmems(this,
+ aTransactionData->mSmallShmems);
+ wr::IpcResourceUpdateQueue::ReleaseShmems(this,
+ aTransactionData->mLargeShmems);
+ }
+ return IPC_OK();
+ }
+
+ LOG("WebRenderBridgeParent::RecvEmptyTransaction() PipelineId %" PRIx64
+ " Id %" PRIx64 " root %d",
+ wr::AsUint64(mPipelineId), wr::AsUint64(mApi->GetId()),
+ IsRootWebRenderBridgeParent());
+
+ if (!IsRootWebRenderBridgeParent()) {
+ CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::URL, aTxnURL);
+ }
+
+ AUTO_PROFILER_TRACING_MARKER("Paint", "EmptyTransaction", GRAPHICS);
+ UpdateFwdTransactionId(aFwdTransactionId);
+
+ // This ensures that destroy operations are always processed. It is not safe
+ // to early-return without doing so.
+ AutoWebRenderBridgeParentAsyncMessageSender autoAsyncMessageSender(
+ this, &aToDestroy);
+
+ UpdateAPZFocusState(aFocusTarget);
+
+ bool scheduleAnyComposite = false;
+ wr::RenderReasons renderReasons = wr::RenderReasons::NONE;
+
+ bool success = true;
+ if (aTransactionData) {
+ bool scheduleComposite = false;
+ success =
+ ProcessEmptyTransactionUpdates(*aTransactionData, &scheduleComposite);
+ scheduleAnyComposite = scheduleAnyComposite || scheduleComposite;
+ renderReasons |= wr::RenderReasons::RESOURCE_UPDATE;
+ }
+
+ // If we are going to kick off a new composite as a result of this
+ // transaction, or if there are already composite-triggering pending
+ // transactions inflight, then set sendDidComposite to false because we will
+ // send the DidComposite message after the composite occurs.
+ // If there are no pending transactions and we're not going to do a
+ // composite, then we leave sendDidComposite as true so we just send
+ // the DidComposite notification now.
+ bool sendDidComposite =
+ !scheduleAnyComposite && mPendingTransactionIds.empty();
+
+ // Only register a value for CONTENT_FRAME_TIME telemetry if we actually drew
+ // something. It is for consistency with disabling WebRender.
+ HoldPendingTransactionId(mWrEpoch, aTransactionId, false, aVsyncId,
+ aVsyncStartTime, aRefreshStartTime, aTxnStartTime,
+ aTxnURL, aFwdTime,
+ /* aIsFirstPaint */ false, std::move(aPayloads),
+ /* aUseForTelemetry */ scheduleAnyComposite);
+
+ if (scheduleAnyComposite) {
+ ScheduleGenerateFrame(renderReasons);
+ } else if (sendDidComposite) {
+ // The only thing in the pending transaction id queue should be the entry
+ // we just added, and now we're going to pretend we rendered it
+ MOZ_ASSERT(mPendingTransactionIds.size() == 1);
+ if (CompositorBridgeParent* cbp = GetRootCompositorBridgeParent()) {
+ TimeStamp now = TimeStamp::Now();
+ cbp->NotifyPipelineRendered(mPipelineId, mWrEpoch, VsyncId(), now, now,
+ now);
+ }
+ }
+
+ if (aTransactionData) {
+ wr::IpcResourceUpdateQueue::ReleaseShmems(this,
+ aTransactionData->mSmallShmems);
+ wr::IpcResourceUpdateQueue::ReleaseShmems(this,
+ aTransactionData->mLargeShmems);
+ }
+
+ if (mRemoteTextureTxnScheduler) {
+ mRemoteTextureTxnScheduler->NotifyTxn(aFwdTransactionId);
+ }
+
+ if (!success) {
+ return IPC_FAIL(this, "Failed to process empty transaction update.");
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvSetFocusTarget(
+ const FocusTarget& aFocusTarget) {
+ UpdateAPZFocusState(aFocusTarget);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvParentCommands(
+ const wr::IdNamespace& aIdNamespace,
+ nsTArray<WebRenderParentCommand>&& aCommands) {
+ if (mDestroyed) {
+ return IPC_OK();
+ }
+
+ const bool isValidMessage = aIdNamespace == mIdNamespace;
+ if (!isValidMessage) {
+ return IPC_OK();
+ }
+
+ LOG("WebRenderBridgeParent::RecvParentCommands() PipelineId %" PRIx64
+ " Id %" PRIx64 " root %d",
+ wr::AsUint64(mPipelineId), wr::AsUint64(mApi->GetId()),
+ IsRootWebRenderBridgeParent());
+
+ wr::TransactionBuilder txn(mApi);
+ txn.SetLowPriority(!IsRootWebRenderBridgeParent());
+ bool success = ProcessWebRenderParentCommands(aCommands, txn);
+ mApi->SendTransaction(txn);
+
+ if (!success) {
+ return IPC_FAIL(this, "Invalid parent command found");
+ }
+
+ return IPC_OK();
+}
+
+bool WebRenderBridgeParent::ProcessWebRenderParentCommands(
+ const nsTArray<WebRenderParentCommand>& aCommands,
+ wr::TransactionBuilder& aTxn) {
+ // Transaction for async image pipeline that uses ImageBridge always need to
+ // be non low priority.
+ wr::TransactionBuilder txnForImageBridge(mApi->GetRootAPI());
+ wr::AutoTransactionSender sender(mApi->GetRootAPI(), &txnForImageBridge);
+
+ bool success = true;
+ for (nsTArray<WebRenderParentCommand>::index_type i = 0;
+ i < aCommands.Length(); ++i) {
+ const WebRenderParentCommand& cmd = aCommands[i];
+ switch (cmd.type()) {
+ case WebRenderParentCommand::TOpAddPipelineIdForCompositable: {
+ const OpAddPipelineIdForCompositable& op =
+ cmd.get_OpAddPipelineIdForCompositable();
+ AddPipelineIdForCompositable(op.pipelineId(), op.handle(), op.owner(),
+ aTxn, txnForImageBridge);
+ break;
+ }
+ case WebRenderParentCommand::TOpRemovePipelineIdForCompositable: {
+ const OpRemovePipelineIdForCompositable& op =
+ cmd.get_OpRemovePipelineIdForCompositable();
+ RemovePipelineIdForCompositable(op.pipelineId(), aTxn);
+ break;
+ }
+ case WebRenderParentCommand::TOpReleaseTextureOfImage: {
+ const OpReleaseTextureOfImage& op = cmd.get_OpReleaseTextureOfImage();
+ ReleaseTextureOfImage(op.key());
+ break;
+ }
+ case WebRenderParentCommand::TOpUpdateAsyncImagePipeline: {
+ const OpUpdateAsyncImagePipeline& op =
+ cmd.get_OpUpdateAsyncImagePipeline();
+
+ auto* pendingOps = mApi->GetPendingAsyncImagePipelineOps(aTxn);
+ auto* pendingRemotetextures = mApi->GetPendingRemoteTextureInfoList();
+
+ mAsyncImageManager->UpdateAsyncImagePipeline(
+ op.pipelineId(), op.scBounds(), op.rotation(), op.filter(),
+ op.mixBlendMode());
+ MOZ_ASSERT_IF(IsRootWebRenderBridgeParent(), !pendingRemotetextures);
+ mAsyncImageManager->ApplyAsyncImageForPipeline(
+ op.pipelineId(), aTxn, txnForImageBridge, pendingOps,
+ pendingRemotetextures);
+ break;
+ }
+ case WebRenderParentCommand::TOpUpdatedAsyncImagePipeline: {
+ const OpUpdatedAsyncImagePipeline& op =
+ cmd.get_OpUpdatedAsyncImagePipeline();
+ aTxn.InvalidateRenderedFrame(wr::RenderReasons::ASYNC_IMAGE);
+
+ auto* pendingOps = mApi->GetPendingAsyncImagePipelineOps(aTxn);
+ auto* pendingRemotetextures = mApi->GetPendingRemoteTextureInfoList();
+
+ MOZ_ASSERT_IF(IsRootWebRenderBridgeParent(), !pendingRemotetextures);
+ mAsyncImageManager->ApplyAsyncImageForPipeline(
+ op.pipelineId(), aTxn, txnForImageBridge, pendingOps,
+ pendingRemotetextures);
+ break;
+ }
+ case WebRenderParentCommand::TCompositableOperation: {
+ if (!ReceiveCompositableUpdate(cmd.get_CompositableOperation())) {
+ NS_ERROR("ReceiveCompositableUpdate failed");
+ }
+ break;
+ }
+ case WebRenderParentCommand::TOpAddCompositorAnimations: {
+ const OpAddCompositorAnimations& op =
+ cmd.get_OpAddCompositorAnimations();
+ CompositorAnimations data(std::move(op.data()));
+ // AnimationHelper::GetNextCompositorAnimationsId() encodes the child
+ // process PID in the upper 32 bits of the id, verify that this is as
+ // expected.
+ if ((data.id() >> 32) != (uint64_t)OtherPid()) {
+ gfxCriticalNote << "TOpAddCompositorAnimations bad id";
+ success = false;
+ continue;
+ }
+ if (data.animations().Length()) {
+ if (RefPtr<OMTASampler> sampler = GetOMTASampler()) {
+ sampler->SetAnimations(data.id(), GetLayersId(), data.animations());
+ const auto activeAnim = mActiveAnimations.find(data.id());
+ if (activeAnim == mActiveAnimations.end()) {
+ mActiveAnimations.emplace(data.id(), mWrEpoch);
+ } else {
+ // Update wr::Epoch if the animation already exists.
+ activeAnim->second = mWrEpoch;
+ }
+ }
+ }
+ break;
+ }
+ default: {
+ // other commands are handle on the child
+ break;
+ }
+ }
+ }
+
+ MOZ_ASSERT(success);
+ return success;
+}
+
+void WebRenderBridgeParent::FlushSceneBuilds() {
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+
+ // Since we are sending transactions through the scene builder thread, we need
+ // to block until all the inflight transactions have been processed. This
+ // flush message blocks until all previously sent scenes have been built
+ // and received by the render backend thread.
+ mApi->FlushSceneBuilder();
+ // The post-swap hook for async-scene-building calls the
+ // ScheduleRenderOnCompositorThread function from the scene builder thread,
+ // which then triggers a call to ScheduleGenerateFrame() on the compositor
+ // thread. But since *this* function is running on the compositor thread,
+ // that scheduling will not happen until this call stack unwinds (or we
+ // could spin a nested event loop, but that's more messy). Instead, we
+ // simulate it ourselves by calling ScheduleGenerateFrame() directly.
+ // Note also that the post-swap hook will run and do another
+ // ScheduleGenerateFrame() after we unwind here, so we will end up with an
+ // extra render/composite that is probably avoidable, but in practice we
+ // shouldn't be calling this function all that much in production so this
+ // is probably fine. If it becomes an issue we can add more state tracking
+ // machinery to optimize it away.
+ ScheduleGenerateFrame(wr::RenderReasons::FLUSH);
+}
+
+void WebRenderBridgeParent::FlushFrameGeneration(wr::RenderReasons aReasons) {
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ MOZ_ASSERT(IsRootWebRenderBridgeParent()); // This function is only useful on
+ // the root WRBP
+
+ // This forces a new GenerateFrame transaction to be sent to the render
+ // backend thread, if one is pending. This doesn't block on any other threads.
+ if (mCompositorScheduler->NeedsComposite()) {
+ mCompositorScheduler->CancelCurrentCompositeTask();
+ // Update timestamp of scheduler for APZ and animation.
+ mCompositorScheduler->UpdateLastComposeTime();
+ MaybeGenerateFrame(VsyncId(), /* aForceGenerateFrame */ true,
+ aReasons | wr::RenderReasons::FLUSH);
+ }
+}
+
+void WebRenderBridgeParent::FlushFramePresentation() {
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+
+ // This sends a message to the render backend thread to send a message
+ // to the renderer thread, and waits for that message to be processed. So
+ // this effectively blocks on the render backend and renderer threads,
+ // following the same codepath that WebRender takes to render and composite
+ // a frame.
+ mApi->WaitFlushed();
+}
+
+void WebRenderBridgeParent::DisableNativeCompositor() {
+ // Make sure that SceneBuilder thread does not have a task.
+ mApi->FlushSceneBuilder();
+ // Disable WebRender's native compositor usage
+ mApi->EnableNativeCompositor(false);
+ // Ensure we generate and render a frame immediately.
+ ScheduleForcedGenerateFrame(wr::RenderReasons::CONFIG_CHANGE);
+
+ mDisablingNativeCompositor = true;
+}
+
+void WebRenderBridgeParent::UpdateQualitySettings() {
+ if (!IsRootWebRenderBridgeParent()) {
+ MOZ_ASSERT_UNREACHABLE("unexpected to be called");
+ return;
+ }
+ wr::TransactionBuilder txn(mApi);
+ txn.UpdateQualitySettings(gfxVars::ForceSubpixelAAWherePossible());
+ mApi->SendTransaction(txn);
+}
+
+void WebRenderBridgeParent::UpdateDebugFlags() {
+ if (!IsRootWebRenderBridgeParent()) {
+ MOZ_ASSERT_UNREACHABLE("unexpected to be called");
+ return;
+ }
+
+ mApi->UpdateDebugFlags(gfxVars::WebRenderDebugFlags());
+}
+
+void WebRenderBridgeParent::UpdateProfilerUI() {
+ if (!IsRootWebRenderBridgeParent()) {
+ MOZ_ASSERT_UNREACHABLE("unexpected to be called");
+ return;
+ }
+
+ nsCString uiString = gfxVars::GetWebRenderProfilerUIOrDefault();
+ mApi->SetProfilerUI(uiString);
+}
+
+void WebRenderBridgeParent::UpdateParameters() {
+ if (!IsRootWebRenderBridgeParent()) {
+ MOZ_ASSERT_UNREACHABLE("unexpected to be called");
+ return;
+ }
+
+ uint32_t count = gfxVars::WebRenderBatchingLookback();
+ mApi->SetBatchingLookback(count);
+ mApi->SetInt(wr::IntParameter::BatchedUploadThreshold,
+ gfxVars::WebRenderBatchedUploadThreshold());
+
+ mBlobTileSize = gfxVars::WebRenderBlobTileSize();
+}
+
+void WebRenderBridgeParent::UpdateBoolParameters() {
+ if (!IsRootWebRenderBridgeParent()) {
+ MOZ_ASSERT_UNREACHABLE("unexpected to be called");
+ return;
+ }
+
+ uint32_t bits = gfxVars::WebRenderBoolParameters();
+ uint32_t changedBits = mBoolParameterBits ^ bits;
+
+ for (auto paramName : MakeEnumeratedRange(wr::BoolParameter::Sentinel)) {
+ uint32_t i = (uint32_t)paramName;
+ if (changedBits & (1 << i)) {
+ bool value = (bits & (1 << i)) != 0;
+ mApi->SetBool(paramName, value);
+ }
+ }
+ mBoolParameterBits = bits;
+}
+
+#if defined(MOZ_WIDGET_ANDROID)
+void WebRenderBridgeParent::RequestScreenPixels(
+ UiCompositorControllerParent* aController) {
+ mScreenPixelsTarget = aController;
+}
+
+void WebRenderBridgeParent::MaybeCaptureScreenPixels() {
+ if (!mScreenPixelsTarget) {
+ return;
+ }
+
+ if (mDestroyed) {
+ return;
+ }
+
+ if (auto* cbp = GetRootCompositorBridgeParent()) {
+ cbp->FlushPendingWrTransactionEventsWithWait();
+ }
+
+ // This function should only get called in the root WRBP.
+ MOZ_ASSERT(IsRootWebRenderBridgeParent());
+# ifdef DEBUG
+ CompositorBridgeParent* cbp = GetRootCompositorBridgeParent();
+ MOZ_ASSERT(cbp && !cbp->IsPaused());
+# endif
+
+ SurfaceFormat format = SurfaceFormat::R8G8B8A8; // On android we use RGBA8
+ auto client_size = mWidget->GetClientSize();
+ size_t buffer_size =
+ client_size.width * client_size.height * BytesPerPixel(format);
+
+ ipc::Shmem mem;
+ if (!mScreenPixelsTarget->AllocPixelBuffer(buffer_size, &mem)) {
+ // Failed to alloc shmem, Just bail out.
+ return;
+ }
+
+ IntSize size(client_size.width, client_size.height);
+
+ bool needsYFlip = false;
+ mApi->Readback(TimeStamp::Now(), size, format,
+ Range<uint8_t>(mem.get<uint8_t>(), buffer_size), &needsYFlip);
+
+ Unused << mScreenPixelsTarget->SendScreenPixels(
+ std::move(mem), ScreenIntSize(client_size.width, client_size.height),
+ needsYFlip);
+
+ mScreenPixelsTarget = nullptr;
+}
+#endif
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvGetSnapshot(
+ NotNull<PTextureParent*> aTexture, bool* aNeedsYFlip) {
+ *aNeedsYFlip = false;
+ CompositorBridgeParent* cbp = GetRootCompositorBridgeParent();
+ if (mDestroyed || !cbp || cbp->IsPaused()) {
+ return IPC_OK();
+ }
+
+ if (auto* cbp = GetRootCompositorBridgeParent()) {
+ cbp->FlushPendingWrTransactionEventsWithWait();
+ }
+
+ LOG("WebRenderBridgeParent::RecvGetSnapshot() PipelineId %" PRIx64
+ " Id %" PRIx64 " root %d",
+ wr::AsUint64(mPipelineId), wr::AsUint64(mApi->GetId()),
+ IsRootWebRenderBridgeParent());
+
+ // This function should only get called in the root WRBP. If this function
+ // gets called in a non-root WRBP, we will set mForceRendering in this WRBP
+ // but it will have no effect because CompositeToTarget (which reads the
+ // flag) only gets invoked in the root WRBP. So we assert that this is the
+ // root WRBP (i.e. has a non-null mWidget) to catch violations of this rule.
+ MOZ_ASSERT(IsRootWebRenderBridgeParent());
+
+ RefPtr<TextureHost> texture = TextureHost::AsTextureHost(aTexture);
+ if (!texture) {
+ // We kill the content process rather than have it continue with an invalid
+ // snapshot, that may be too harsh and we could decide to return some sort
+ // of error to the child process and let it deal with it...
+ return IPC_FAIL_NO_REASON(this);
+ }
+
+ // XXX Add other TextureHost supports.
+ // Only BufferTextureHost is supported now.
+ BufferTextureHost* bufferTexture = texture->AsBufferTextureHost();
+ if (!bufferTexture) {
+ // We kill the content process rather than have it continue with an invalid
+ // snapshot, that may be too harsh and we could decide to return some sort
+ // of error to the child process and let it deal with it...
+ return IPC_FAIL_NO_REASON(this);
+ }
+
+ TimeStamp start = TimeStamp::Now();
+ MOZ_ASSERT(bufferTexture->GetBufferDescriptor().type() ==
+ BufferDescriptor::TRGBDescriptor);
+ DebugOnly<uint32_t> stride = ImageDataSerializer::GetRGBStride(
+ bufferTexture->GetBufferDescriptor().get_RGBDescriptor());
+ uint8_t* buffer = bufferTexture->GetBuffer();
+ IntSize size = bufferTexture->GetSize();
+
+ MOZ_ASSERT(buffer);
+ // For now the only formats we get here are RGBA and BGRA, and code below is
+ // assuming a bpp of 4. If we allow other formats, the code needs adjusting
+ // accordingly.
+ MOZ_ASSERT(BytesPerPixel(bufferTexture->GetFormat()) == 4);
+ uint32_t buffer_size = size.width * size.height * 4;
+
+ // Assert the stride of the buffer is what webrender expects
+ MOZ_ASSERT((uint32_t)(size.width * 4) == stride);
+
+ FlushSceneBuilds();
+ FlushFrameGeneration(wr::RenderReasons::SNAPSHOT);
+ mApi->Readback(start, size, bufferTexture->GetFormat(),
+ Range<uint8_t>(buffer, buffer_size), aNeedsYFlip);
+
+ return IPC_OK();
+}
+
+void WebRenderBridgeParent::AddPipelineIdForCompositable(
+ const wr::PipelineId& aPipelineId, const CompositableHandle& aHandle,
+ const CompositableHandleOwner& aOwner, wr::TransactionBuilder& aTxn,
+ wr::TransactionBuilder& aTxnForImageBridge) {
+ if (mDestroyed) {
+ return;
+ }
+
+ MOZ_ASSERT(mAsyncCompositables.find(wr::AsUint64(aPipelineId)) ==
+ mAsyncCompositables.end());
+
+ RefPtr<CompositableHost> host;
+ switch (aOwner) {
+ case CompositableHandleOwner::WebRenderBridge:
+ host = FindCompositable(aHandle);
+ break;
+ case CompositableHandleOwner::ImageBridge: {
+ RefPtr<ImageBridgeParent> imageBridge =
+ ImageBridgeParent::GetInstance(OtherPid());
+ if (!imageBridge) {
+ return;
+ }
+ host = imageBridge->FindCompositable(aHandle);
+ break;
+ }
+ }
+
+ if (!host) {
+ return;
+ }
+
+ WebRenderImageHost* wrHost = host->AsWebRenderImageHost();
+ MOZ_ASSERT(wrHost);
+ if (!wrHost) {
+ gfxCriticalNote
+ << "Incompatible CompositableHost at WebRenderBridgeParent.";
+ }
+
+ if (!wrHost) {
+ return;
+ }
+
+ wrHost->SetWrBridge(aPipelineId, this);
+ mAsyncCompositables.emplace(wr::AsUint64(aPipelineId), wrHost);
+ mAsyncImageManager->AddAsyncImagePipeline(aPipelineId, wrHost);
+
+ // If this is being called from WebRenderBridgeParent::RecvSetDisplayList,
+ // then aTxn might contain a display list that references pipelines that
+ // we just added to the async image manager.
+ // If we send the display list alone then WR will not yet have the content for
+ // the pipelines and so it will emit errors; the SetEmptyDisplayList call
+ // below ensure that we provide its content to WR as part of the same
+ // transaction.
+ mAsyncImageManager->SetEmptyDisplayList(aPipelineId, aTxn,
+ aTxnForImageBridge);
+}
+
+void WebRenderBridgeParent::RemovePipelineIdForCompositable(
+ const wr::PipelineId& aPipelineId, wr::TransactionBuilder& aTxn) {
+ if (mDestroyed) {
+ return;
+ }
+
+ auto it = mAsyncCompositables.find(wr::AsUint64(aPipelineId));
+ if (it == mAsyncCompositables.end()) {
+ return;
+ }
+ RefPtr<WebRenderImageHost>& wrHost = it->second;
+
+ wrHost->ClearWrBridge(aPipelineId, this);
+ mAsyncImageManager->RemoveAsyncImagePipeline(aPipelineId, aTxn);
+ aTxn.RemovePipeline(aPipelineId);
+ mAsyncCompositables.erase(wr::AsUint64(aPipelineId));
+}
+
+void WebRenderBridgeParent::DeleteImage(const ImageKey& aKey,
+ wr::TransactionBuilder& aUpdates) {
+ if (mDestroyed) {
+ return;
+ }
+
+ auto it = mSharedSurfaceIds.find(wr::AsUint64(aKey));
+ if (it != mSharedSurfaceIds.end()) {
+ mAsyncImageManager->HoldExternalImage(mPipelineId, mWrEpoch, it->second);
+ mSharedSurfaceIds.erase(it);
+ }
+
+ aUpdates.DeleteImage(aKey);
+}
+
+void WebRenderBridgeParent::ReleaseTextureOfImage(const wr::ImageKey& aKey) {
+ if (mDestroyed) {
+ return;
+ }
+
+ uint64_t id = wr::AsUint64(aKey);
+ CompositableTextureHostRef texture;
+ WebRenderTextureHost* wrTexture = nullptr;
+
+ auto it = mTextureHosts.find(id);
+ if (it != mTextureHosts.end()) {
+ wrTexture = (*it).second->AsWebRenderTextureHost();
+ }
+ if (wrTexture) {
+ mAsyncImageManager->HoldExternalImage(mPipelineId, mWrEpoch, wrTexture);
+ }
+ mTextureHosts.erase(id);
+}
+
+void WebRenderBridgeParent::MaybeNotifyOfLayers(
+ wr::TransactionBuilder& aBuilder, bool aWillHaveLayers) {
+ if (mLastNotifiedHasLayers == aWillHaveLayers) {
+ return;
+ }
+
+ aBuilder.Notify(wr::Checkpoint::SceneBuilt,
+ MakeUnique<ScheduleObserveLayersUpdate>(
+ mCompositorBridge, GetLayersId(), aWillHaveLayers));
+ mLastNotifiedHasLayers = aWillHaveLayers;
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvClearCachedResources() {
+ if (mDestroyed) {
+ return IPC_OK();
+ }
+
+ LOG("WebRenderBridgeParent::RecvClearCachedResources() PipelineId %" PRIx64
+ " Id %" PRIx64 " root %d",
+ wr::AsUint64(mPipelineId), wr::AsUint64(mApi->GetId()),
+ IsRootWebRenderBridgeParent());
+
+ // Clear resources
+ wr::TransactionBuilder txn(mApi);
+ txn.SetLowPriority(true);
+ txn.ClearDisplayList(GetNextWrEpoch(), mPipelineId);
+ MaybeNotifyOfLayers(txn, false);
+ mApi->SendTransaction(txn);
+
+ // Schedule generate frame to clean up Pipeline
+ ScheduleGenerateFrame(wr::RenderReasons::CLEAR_RESOURCES);
+
+ ClearAnimationResources();
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvClearAnimationResources() {
+ if (!mDestroyed) {
+ ClearAnimationResources();
+ }
+
+ return IPC_OK();
+}
+
+wr::Epoch WebRenderBridgeParent::UpdateWebRender(
+ CompositorVsyncScheduler* aScheduler, RefPtr<wr::WebRenderAPI>&& aApi,
+ AsyncImagePipelineManager* aImageMgr,
+ const TextureFactoryIdentifier& aTextureFactoryIdentifier) {
+ MOZ_ASSERT(!IsRootWebRenderBridgeParent());
+ MOZ_ASSERT(aScheduler);
+ MOZ_ASSERT(aApi);
+ MOZ_ASSERT(aImageMgr);
+
+ if (mDestroyed) {
+ return mWrEpoch;
+ }
+
+ // Update id name space to identify obsoleted keys.
+ // Since usage of invalid keys could cause crash in webrender.
+ mIdNamespace = aApi->GetNamespace();
+ // XXX Remove it when webrender supports sharing/moving Keys between different
+ // webrender instances.
+ // XXX It requests client to update/reallocate webrender related resources,
+ // but parent side does not wait end of the update.
+ // The code could become simpler if we could serialise old keys deallocation
+ // and new keys allocation. But we do not do it, it is because client side
+ // deallocate old layers/webrender keys after new layers/webrender keys
+ // allocation. Without client side's layout refactoring, we could not finish
+ // all old layers/webrender keys removals before new layer/webrender keys
+ // allocation. In future, we could address the problem.
+ Unused << SendWrUpdated(mIdNamespace, aTextureFactoryIdentifier);
+ CompositorBridgeParentBase* cBridge = mCompositorBridge;
+ // XXX Stop to clear resources if webreder supports resources sharing between
+ // different webrender instances.
+ ClearResources();
+ mCompositorBridge = cBridge;
+ mCompositorScheduler = aScheduler;
+ mApi = aApi;
+ mAsyncImageManager = aImageMgr;
+
+ // Register pipeline to updated AsyncImageManager.
+ mAsyncImageManager->AddPipeline(mPipelineId, this);
+
+ LOG("WebRenderBridgeParent::UpdateWebRender() PipelineId %" PRIx64
+ " Id %" PRIx64 " root %d",
+ wr::AsUint64(mPipelineId), wr::AsUint64(mApi->GetId()),
+ IsRootWebRenderBridgeParent());
+
+ return GetNextWrEpoch(); // Update webrender epoch
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvInvalidateRenderedFrame() {
+ // This function should only get called in the root WRBP
+ MOZ_ASSERT(IsRootWebRenderBridgeParent());
+ LOG("WebRenderBridgeParent::RecvInvalidateRenderedFrame() PipelineId %" PRIx64
+ " Id %" PRIx64 " root %d",
+ wr::AsUint64(mPipelineId), wr::AsUint64(mApi->GetId()),
+ IsRootWebRenderBridgeParent());
+
+ InvalidateRenderedFrame(wr::RenderReasons::WIDGET);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvScheduleComposite(
+ const wr::RenderReasons& aReasons) {
+ LOG("WebRenderBridgeParent::RecvScheduleComposite() PipelineId %" PRIx64
+ " Id %" PRIx64 " root %d",
+ wr::AsUint64(mPipelineId), wr::AsUint64(mApi->GetId()),
+ IsRootWebRenderBridgeParent());
+
+ // Caller of LayerManager::ScheduleComposite() expects that it trigger
+ // composite. Then we do not want to skip generate frame.
+ ScheduleForcedGenerateFrame(aReasons);
+ return IPC_OK();
+}
+
+void WebRenderBridgeParent::InvalidateRenderedFrame(
+ wr::RenderReasons aReasons) {
+ if (mDestroyed) {
+ return;
+ }
+
+ wr::TransactionBuilder fastTxn(mApi, /* aUseSceneBuilderThread */ false);
+ fastTxn.InvalidateRenderedFrame(aReasons);
+ mApi->SendTransaction(fastTxn);
+}
+
+void WebRenderBridgeParent::ScheduleForcedGenerateFrame(
+ wr::RenderReasons aReasons) {
+ if (mDestroyed) {
+ return;
+ }
+
+ InvalidateRenderedFrame(aReasons);
+ ScheduleGenerateFrame(aReasons);
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvCapture() {
+ if (!mDestroyed) {
+ mApi->Capture();
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvStartCaptureSequence(
+ const nsACString& aPath, const uint32_t& aFlags) {
+ if (!mDestroyed) {
+ mApi->StartCaptureSequence(aPath, aFlags);
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvStopCaptureSequence() {
+ if (!mDestroyed) {
+ mApi->StopCaptureSequence();
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvSyncWithCompositor() {
+ LOG("WebRenderBridgeParent::RecvSyncWithCompositor() PipelineId %" PRIx64
+ " Id %" PRIx64 " root %d",
+ wr::AsUint64(mPipelineId), wr::AsUint64(mApi->GetId()),
+ IsRootWebRenderBridgeParent());
+
+ if (mDestroyed) {
+ return IPC_OK();
+ }
+
+ FlushSceneBuilds();
+ if (RefPtr<WebRenderBridgeParent> root = GetRootWebRenderBridgeParent()) {
+ root->FlushFrameGeneration(wr::RenderReasons::CONTENT_SYNC);
+ }
+ FlushFramePresentation();
+ // Finally, we force the AsyncImagePipelineManager to handle all the
+ // pipeline updates produced in the last step, so that it frees any
+ // unneeded textures. Then we can return from this sync IPC call knowing
+ // that we've done everything we can to flush stuff on the compositor.
+ mAsyncImageManager->ProcessPipelineUpdates();
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvSetConfirmedTargetAPZC(
+ const uint64_t& aBlockId, nsTArray<ScrollableLayerGuid>&& aTargets) {
+ for (size_t i = 0; i < aTargets.Length(); i++) {
+ // Guard against bad data from hijacked child processes
+ if (aTargets[i].mLayersId != GetLayersId()) {
+ NS_ERROR(
+ "Unexpected layers id in RecvSetConfirmedTargetAPZC; dropping "
+ "message...");
+ return IPC_FAIL(this, "Bad layers id");
+ }
+ }
+
+ if (mDestroyed) {
+ return IPC_OK();
+ }
+ mCompositorBridge->SetConfirmedTargetAPZC(GetLayersId(), aBlockId,
+ std::move(aTargets));
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvSetTestSampleTime(
+ const TimeStamp& aTime) {
+ if (mDestroyed) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+
+ if (!mCompositorBridge->SetTestSampleTime(GetLayersId(), aTime)) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ if (RefPtr<OMTASampler> sampler = GetOMTASampler()) {
+ sampler->EnterTestMode();
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvLeaveTestMode() {
+ if (mDestroyed) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+
+ mCompositorBridge->LeaveTestMode(GetLayersId());
+ if (RefPtr<OMTASampler> sampler = GetOMTASampler()) {
+ sampler->LeaveTestMode();
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvGetAnimationValue(
+ const uint64_t& aCompositorAnimationsId, OMTAValue* aValue) {
+ if (mDestroyed) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+
+ if (RefPtr<OMTASampler> sampler = GetOMTASampler()) {
+ Maybe<TimeStamp> testingTimeStamp;
+ if (CompositorBridgeParent* cbp = GetRootCompositorBridgeParent()) {
+ testingTimeStamp = cbp->GetTestingTimeStamp();
+ }
+
+ sampler->SampleForTesting(testingTimeStamp);
+ *aValue = sampler->GetOMTAValue(aCompositorAnimationsId);
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvSetAsyncScrollOffset(
+ const ScrollableLayerGuid::ViewID& aScrollId, const float& aX,
+ const float& aY) {
+ if (mDestroyed) {
+ return IPC_OK();
+ }
+ mCompositorBridge->SetTestAsyncScrollOffset(GetLayersId(), aScrollId,
+ CSSPoint(aX, aY));
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvSetAsyncZoom(
+ const ScrollableLayerGuid::ViewID& aScrollId, const float& aZoom) {
+ if (mDestroyed) {
+ return IPC_OK();
+ }
+ mCompositorBridge->SetTestAsyncZoom(GetLayersId(), aScrollId,
+ LayerToParentLayerScale(aZoom));
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvFlushApzRepaints() {
+ if (mDestroyed) {
+ return IPC_OK();
+ }
+ mCompositorBridge->FlushApzRepaints(GetLayersId());
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvGetAPZTestData(
+ APZTestData* aOutData) {
+ if (mDestroyed) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+
+ mCompositorBridge->GetAPZTestData(GetLayersId(), aOutData);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvGetFrameUniformity(
+ FrameUniformityData* aOutData) {
+ if (mDestroyed) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+
+ mCompositorBridge->GetFrameUniformity(GetLayersId(), aOutData);
+ return IPC_OK();
+}
+
+void WebRenderBridgeParent::ActorDestroy(ActorDestroyReason aWhy) { Destroy(); }
+
+void WebRenderBridgeParent::ResetPreviousSampleTime() {
+ if (RefPtr<OMTASampler> sampler = GetOMTASampler()) {
+ sampler->ResetPreviousSampleTime();
+ }
+}
+
+RefPtr<OMTASampler> WebRenderBridgeParent::GetOMTASampler() const {
+ CompositorBridgeParent* cbp = GetRootCompositorBridgeParent();
+ if (!cbp) {
+ return nullptr;
+ }
+ return cbp->GetOMTASampler();
+}
+
+void WebRenderBridgeParent::SetOMTASampleTime() {
+ MOZ_ASSERT(IsRootWebRenderBridgeParent());
+ if (RefPtr<OMTASampler> sampler = GetOMTASampler()) {
+ sampler->SetSampleTime(mCompositorScheduler->GetLastComposeTime().Time());
+ }
+}
+
+void WebRenderBridgeParent::RetrySkippedComposite() {
+ if (!mSkippedComposite) {
+ return;
+ }
+
+ mSkippedComposite = false;
+ if (mCompositorScheduler) {
+ mCompositorScheduler->ScheduleComposition(mSkippedCompositeReasons |
+ RenderReasons::SKIPPED_COMPOSITE);
+ }
+ mSkippedCompositeReasons = wr::RenderReasons::NONE;
+}
+
+void WebRenderBridgeParent::CompositeToTarget(VsyncId aId,
+ wr::RenderReasons aReasons,
+ gfx::DrawTarget* aTarget,
+ const gfx::IntRect* aRect) {
+ // This function should only get called in the root WRBP
+ MOZ_ASSERT(IsRootWebRenderBridgeParent());
+
+ // The two arguments are part of the CompositorVsyncSchedulerOwner API but in
+ // this implementation they should never be non-null.
+ MOZ_ASSERT(aTarget == nullptr);
+ MOZ_ASSERT(aRect == nullptr);
+
+ LOG("WebRenderBridgeParent::CompositeToTarget() PipelineId %" PRIx64
+ " Id %" PRIx64 " root %d",
+ wr::AsUint64(mPipelineId), wr::AsUint64(mApi->GetId()),
+ IsRootWebRenderBridgeParent());
+
+ CompositorBridgeParent* cbp = GetRootCompositorBridgeParent();
+ uint64_t innerWindowId = cbp ? cbp->GetInnerWindowId() : 0;
+ AUTO_PROFILER_TRACING_MARKER_INNERWINDOWID("Paint", "CompositeToTarget",
+ GRAPHICS, innerWindowId);
+
+ bool paused = true;
+ if (cbp) {
+ paused = cbp->IsPaused();
+ }
+
+ if (paused || !mReceivedDisplayList) {
+ ResetPreviousSampleTime();
+ mCompositionOpportunityId = mCompositionOpportunityId.Next();
+ PROFILER_MARKER_TEXT("Discarded composite", GRAPHICS,
+ MarkerInnerWindowId(innerWindowId),
+ paused ? "Paused"_ns : "No display list"_ns);
+ return;
+ }
+
+ mSkippedComposite =
+ wr::RenderThread::Get()->TooManyPendingFrames(mApi->GetId());
+
+ if (mSkippedComposite) {
+ // Render thread is busy, try next time.
+ mSkippedComposite = true;
+ mSkippedCompositeReasons = mSkippedCompositeReasons | aReasons;
+ ResetPreviousSampleTime();
+
+ // Record that we skipped presenting a frame for
+ // all pending transactions that have finished scene building.
+ for (auto& id : mPendingTransactionIds) {
+ if (id.mSceneBuiltTime) {
+ id.mSkippedComposites++;
+ }
+ }
+
+ PROFILER_MARKER_TEXT("SkippedComposite", GRAPHICS,
+ MarkerInnerWindowId(innerWindowId),
+ "Too many pending frames");
+
+ Telemetry::ScalarAdd(Telemetry::ScalarID::GFX_SKIPPED_COMPOSITES, 1);
+
+ return;
+ }
+
+ mCompositionOpportunityId = mCompositionOpportunityId.Next();
+ MaybeGenerateFrame(aId, /* aForceGenerateFrame */ false, aReasons);
+}
+
+TimeDuration WebRenderBridgeParent::GetVsyncInterval() const {
+ // This function should only get called in the root WRBP
+ MOZ_ASSERT(IsRootWebRenderBridgeParent());
+ if (CompositorBridgeParent* cbp = GetRootCompositorBridgeParent()) {
+ return cbp->GetVsyncInterval();
+ }
+ return TimeDuration();
+}
+
+void WebRenderBridgeParent::MaybeGenerateFrame(VsyncId aId,
+ bool aForceGenerateFrame,
+ wr::RenderReasons aReasons) {
+ // This function should only get called in the root WRBP
+ MOZ_ASSERT(IsRootWebRenderBridgeParent());
+ LOG("WebRenderBridgeParent::MaybeGenerateFrame() PipelineId %" PRIx64
+ " Id %" PRIx64 " root %d",
+ wr::AsUint64(mPipelineId), wr::AsUint64(mApi->GetId()),
+ IsRootWebRenderBridgeParent());
+
+ if (CompositorBridgeParent* cbp = GetRootCompositorBridgeParent()) {
+ // Skip WR render during paused state.
+ if (cbp->IsPaused()) {
+ TimeStamp now = TimeStamp::Now();
+ PROFILER_MARKER_TEXT(
+ "SkippedComposite", GRAPHICS,
+ MarkerOptions(MarkerInnerWindowId(cbp->GetInnerWindowId()),
+ MarkerTiming::InstantAt(now)),
+ "CompositorBridgeParent is paused");
+ cbp->NotifyPipelineRendered(mPipelineId, mWrEpoch, VsyncId(), now, now,
+ now);
+ return;
+ }
+ }
+
+ TimeStamp start = TimeStamp::Now();
+
+ // Ensure GenerateFrame is handled on the render backend thread rather
+ // than going through the scene builder thread. That way we continue
+ // generating frames with the old scene even during slow scene builds.
+ wr::TransactionBuilder fastTxn(mApi, false /* useSceneBuilderThread */);
+ // Handle transaction that is related to DisplayList.
+ wr::TransactionBuilder sceneBuilderTxn(mApi);
+ wr::AutoTransactionSender sender(mApi, &sceneBuilderTxn);
+
+ mAsyncImageManager->SetCompositionInfo(start, mCompositionOpportunityId);
+ mAsyncImageManager->ApplyAsyncImagesOfImageBridge(sceneBuilderTxn, fastTxn);
+ mAsyncImageManager->SetCompositionInfo(TimeStamp(),
+ CompositionOpportunityId{});
+
+ if (!mAsyncImageManager->GetCompositeUntilTime().IsNull()) {
+ // Trigger another CompositeToTarget() call because there might be another
+ // frame that we want to generate after this one.
+ // It will check if we actually want to generate the frame or not.
+ mCompositorScheduler->ScheduleComposition(
+ wr::RenderReasons::ASYNC_IMAGE_COMPOSITE_UNTIL);
+ }
+
+ bool generateFrame = !fastTxn.IsEmpty() || aForceGenerateFrame;
+
+ if (mAsyncImageManager->GetAndResetWillGenerateFrame()) {
+ aReasons |= wr::RenderReasons::ASYNC_IMAGE;
+ generateFrame = true;
+ }
+
+ if (!generateFrame) {
+ // Could skip generating frame now.
+ PROFILER_MARKER_TEXT("SkippedComposite", GRAPHICS,
+ MarkerTiming::InstantAt(start),
+ "No reason to generate frame");
+ ResetPreviousSampleTime();
+ return;
+ }
+
+ if (RefPtr<OMTASampler> sampler = GetOMTASampler()) {
+ if (sampler->HasAnimations()) {
+ ScheduleGenerateFrame(wr::RenderReasons::ANIMATED_PROPERTY);
+ }
+ }
+
+ SetOMTASampleTime();
+ SetAPZSampleTime();
+
+#if defined(ENABLE_FRAME_LATENCY_LOG)
+ auto startTime = TimeStamp::Now();
+ mApi->SetFrameStartTime(startTime);
+#endif
+
+ fastTxn.GenerateFrame(aId, aReasons);
+ wr::RenderThread::Get()->IncPendingFrameCount(mApi->GetId(), aId, start);
+
+ mApi->SendTransaction(fastTxn);
+
+#if defined(MOZ_WIDGET_ANDROID)
+ MaybeCaptureScreenPixels();
+#endif
+
+ mMostRecentComposite = TimeStamp::Now();
+
+ // During disabling native compositor, webrender needs to render twice.
+ // Otherwise, browser flashes black.
+ // XXX better fix?
+ if (mDisablingNativeCompositor) {
+ mDisablingNativeCompositor = false;
+
+ // Ensure we generate and render a frame immediately.
+ ScheduleForcedGenerateFrame(aReasons);
+ }
+}
+
+void WebRenderBridgeParent::HoldPendingTransactionId(
+ const wr::Epoch& aWrEpoch, TransactionId aTransactionId,
+ bool aContainsSVGGroup, const VsyncId& aVsyncId,
+ const TimeStamp& aVsyncStartTime, const TimeStamp& aRefreshStartTime,
+ const TimeStamp& aTxnStartTime, const nsACString& aTxnURL,
+ const TimeStamp& aFwdTime, const bool aIsFirstPaint,
+ nsTArray<CompositionPayload>&& aPayloads, const bool aUseForTelemetry) {
+ MOZ_ASSERT(aTransactionId > LastPendingTransactionId());
+ mPendingTransactionIds.push_back(PendingTransactionId(
+ aWrEpoch, aTransactionId, aContainsSVGGroup, aVsyncId, aVsyncStartTime,
+ aRefreshStartTime, aTxnStartTime, aTxnURL, aFwdTime, aIsFirstPaint,
+ aUseForTelemetry, std::move(aPayloads)));
+}
+
+TransactionId WebRenderBridgeParent::LastPendingTransactionId() {
+ TransactionId id{0};
+ if (!mPendingTransactionIds.empty()) {
+ id = mPendingTransactionIds.back().mId;
+ }
+ return id;
+}
+
+void WebRenderBridgeParent::NotifySceneBuiltForEpoch(
+ const wr::Epoch& aEpoch, const TimeStamp& aEndTime) {
+ for (auto& id : mPendingTransactionIds) {
+ if (id.mEpoch.mHandle == aEpoch.mHandle) {
+ id.mSceneBuiltTime = aEndTime;
+ break;
+ }
+ }
+}
+
+void WebRenderBridgeParent::NotifyDidSceneBuild(
+ RefPtr<const wr::WebRenderPipelineInfo> aInfo) {
+ MOZ_ASSERT(IsRootWebRenderBridgeParent());
+ if (!mCompositorScheduler) {
+ return;
+ }
+
+ mAsyncImageManager->SetWillGenerateFrame();
+
+ // If the scheduler has a composite more recent than our last composite (which
+ // we missed), and we're within the threshold ms of the last vsync, then
+ // kick of a late composite.
+ TimeStamp lastVsync = mCompositorScheduler->GetLastVsyncTime();
+ VsyncId lastVsyncId = mCompositorScheduler->GetLastVsyncId();
+ if (lastVsyncId == VsyncId() || !mMostRecentComposite ||
+ mMostRecentComposite >= lastVsync ||
+ ((TimeStamp::Now() - lastVsync).ToMilliseconds() >
+ StaticPrefs::gfx_webrender_late_scenebuild_threshold())) {
+ mCompositorScheduler->ScheduleComposition(wr::RenderReasons::SCENE);
+ return;
+ }
+
+ // Look through all the pipelines contained within the built scene
+ // and check which vsync they initiated from.
+ const auto& info = aInfo->Raw();
+ for (const auto& epoch : info.epochs) {
+ WebRenderBridgeParent* wrBridge = this;
+ if (!(epoch.pipeline_id == PipelineId())) {
+ wrBridge = mAsyncImageManager->GetWrBridge(epoch.pipeline_id);
+ }
+
+ if (wrBridge) {
+ VsyncId startId = wrBridge->GetVsyncIdForEpoch(epoch.epoch);
+ // If any of the pipelines started building on the current vsync (i.e
+ // we did all of display list building and scene building within the
+ // threshold), then don't do an early composite.
+ if (startId == lastVsyncId) {
+ mCompositorScheduler->ScheduleComposition(wr::RenderReasons::SCENE);
+ return;
+ }
+ }
+ }
+
+ CompositeToTarget(mCompositorScheduler->GetLastVsyncId(),
+ wr::RenderReasons::SCENE, nullptr, nullptr);
+}
+
+static Telemetry::HistogramID GetHistogramId(const bool aIsLargePaint,
+ const bool aIsFullDisplayList) {
+ const Telemetry::HistogramID histogramIds[] = {
+ Telemetry::CONTENT_SMALL_PAINT_PHASE_WEIGHT_PARTIAL,
+ Telemetry::CONTENT_LARGE_PAINT_PHASE_WEIGHT_PARTIAL,
+ Telemetry::CONTENT_SMALL_PAINT_PHASE_WEIGHT_FULL,
+ Telemetry::CONTENT_LARGE_PAINT_PHASE_WEIGHT_FULL,
+ };
+
+ return histogramIds[(aIsFullDisplayList * 2) + aIsLargePaint];
+}
+
+static void RecordPaintPhaseTelemetry(wr::RendererStats* aStats) {
+ if (!aStats || !aStats->full_paint) {
+ return;
+ }
+
+ const double geckoDL = aStats->gecko_display_list_time;
+ const double wrDL = aStats->wr_display_list_time;
+ const double sceneBuild = aStats->scene_build_time;
+ const double frameBuild = aStats->frame_build_time;
+ const double totalMs = geckoDL + wrDL + sceneBuild + frameBuild;
+
+ // If the total time was >= 16ms, then it's likely we missed a frame due to
+ // painting. We bucket these metrics separately.
+ const bool isLargePaint = totalMs >= 16.0;
+
+ // Split the results based on display list build type, partial or full.
+ const bool isFullDisplayList = aStats->full_display_list;
+
+ auto AsPercentage = [&](const double aTimeMs) -> double {
+ MOZ_ASSERT(aTimeMs <= totalMs);
+ return (aTimeMs / totalMs) * 100.0;
+ };
+
+ auto RecordKey = [&](const nsCString& aKey, const double aTimeMs) -> void {
+ const auto val = static_cast<uint32_t>(AsPercentage(aTimeMs));
+ const auto histogramId = GetHistogramId(isLargePaint, isFullDisplayList);
+ Telemetry::Accumulate(histogramId, aKey, val);
+ };
+
+ RecordKey("dl"_ns, geckoDL);
+ RecordKey("wrdl"_ns, wrDL);
+ RecordKey("sb"_ns, sceneBuild);
+ RecordKey("fb"_ns, frameBuild);
+}
+
+void WebRenderBridgeParent::FlushTransactionIdsForEpoch(
+ const wr::Epoch& aEpoch, const VsyncId& aCompositeStartId,
+ const TimeStamp& aCompositeStartTime, const TimeStamp& aRenderStartTime,
+ const TimeStamp& aEndTime, UiCompositorControllerParent* aUiController,
+ wr::RendererStats* aStats, nsTArray<FrameStats>& aOutputStats,
+ nsTArray<TransactionId>& aOutputTransactions) {
+ while (!mPendingTransactionIds.empty()) {
+ const auto& transactionId = mPendingTransactionIds.front();
+
+ if (aEpoch.mHandle < transactionId.mEpoch.mHandle) {
+ break;
+ }
+
+ if (!IsRootWebRenderBridgeParent() && !mVsyncRate.IsZero() &&
+ transactionId.mUseForTelemetry) {
+ auto fullPaintTime =
+ transactionId.mSceneBuiltTime
+ ? transactionId.mSceneBuiltTime - transactionId.mTxnStartTime
+ : TimeDuration::FromMilliseconds(0);
+
+ int32_t contentFrameTime = RecordContentFrameTime(
+ transactionId.mVsyncId, transactionId.mVsyncStartTime,
+ transactionId.mTxnStartTime, aCompositeStartId, aEndTime,
+ fullPaintTime, mVsyncRate, transactionId.mContainsSVGGroup, true,
+ aStats);
+
+ RecordPaintPhaseTelemetry(aStats);
+
+ if (StaticPrefs::gfx_logging_slow_frames_enabled_AtStartup() &&
+ contentFrameTime > 200) {
+ aOutputStats.AppendElement(FrameStats(
+ transactionId.mId, aCompositeStartTime, aRenderStartTime, aEndTime,
+ contentFrameTime,
+ aStats ? (double(aStats->resource_upload_time) / 1000000.0) : 0.0,
+ aStats ? (double(aStats->gpu_cache_upload_time) / 1000000.0) : 0.0,
+ transactionId.mTxnStartTime, transactionId.mRefreshStartTime,
+ transactionId.mFwdTime, transactionId.mSceneBuiltTime,
+ transactionId.mSkippedComposites, transactionId.mTxnURL));
+ }
+ }
+
+#if defined(ENABLE_FRAME_LATENCY_LOG)
+ if (transactionId.mRefreshStartTime) {
+ int32_t latencyMs =
+ lround((aEndTime - transactionId.mRefreshStartTime).ToMilliseconds());
+ printf_stderr(
+ "From transaction start to end of generate frame latencyMs %d this "
+ "%p\n",
+ latencyMs, this);
+ }
+ if (transactionId.mFwdTime) {
+ int32_t latencyMs =
+ lround((aEndTime - transactionId.mFwdTime).ToMilliseconds());
+ printf_stderr(
+ "From forwarding transaction to end of generate frame latencyMs %d "
+ "this %p\n",
+ latencyMs, this);
+ }
+#endif
+
+ if (aUiController && transactionId.mIsFirstPaint) {
+ aUiController->NotifyFirstPaint();
+ }
+
+ RecordCompositionPayloadsPresented(aEndTime, transactionId.mPayloads);
+
+ aOutputTransactions.AppendElement(transactionId.mId);
+ mPendingTransactionIds.pop_front();
+ }
+}
+
+LayersId WebRenderBridgeParent::GetLayersId() const {
+ return wr::AsLayersId(mPipelineId);
+}
+
+void WebRenderBridgeParent::ScheduleGenerateFrame(wr::RenderReasons aReasons) {
+ if (mCompositorScheduler) {
+ mAsyncImageManager->SetWillGenerateFrame();
+ mCompositorScheduler->ScheduleComposition(aReasons);
+ }
+}
+
+void WebRenderBridgeParent::FlushRendering(wr::RenderReasons aReasons,
+ bool aWaitForPresent) {
+ if (mDestroyed) {
+ return;
+ }
+
+ // This gets called during e.g. window resizes, so we need to flush the
+ // scene (which has the display list at the new window size).
+ FlushSceneBuilds();
+ FlushFrameGeneration(aReasons);
+ if (aWaitForPresent) {
+ FlushFramePresentation();
+ }
+}
+
+ipc::IPCResult WebRenderBridgeParent::RecvSetDefaultClearColor(
+ const uint32_t& aColor) {
+ SetClearColor(gfx::DeviceColor::FromABGR(aColor));
+ return IPC_OK();
+}
+
+void WebRenderBridgeParent::SetClearColor(const gfx::DeviceColor& aColor) {
+ MOZ_ASSERT(IsRootWebRenderBridgeParent());
+
+ if (!IsRootWebRenderBridgeParent() || mDestroyed) {
+ return;
+ }
+
+ mApi->SetClearColor(aColor);
+}
+
+void WebRenderBridgeParent::Pause() {
+ MOZ_ASSERT(IsRootWebRenderBridgeParent());
+ LOG("WebRenderBridgeParent::Pause() PipelineId %" PRIx64 " Id %" PRIx64
+ " root %d",
+ wr::AsUint64(mPipelineId), wr::AsUint64(mApi->GetId()),
+ IsRootWebRenderBridgeParent());
+
+ if (!IsRootWebRenderBridgeParent() || mDestroyed) {
+ return;
+ }
+
+ mApi->Pause();
+}
+
+bool WebRenderBridgeParent::Resume() {
+ MOZ_ASSERT(IsRootWebRenderBridgeParent());
+ LOG("WebRenderBridgeParent::Resume() PipelineId %" PRIx64 " Id %" PRIx64
+ " root %d",
+ wr::AsUint64(mPipelineId), wr::AsUint64(mApi->GetId()),
+ IsRootWebRenderBridgeParent());
+
+ if (!IsRootWebRenderBridgeParent() || mDestroyed) {
+ return false;
+ }
+
+ if (!mApi->Resume()) {
+ return false;
+ }
+
+ // Ensure we generate and render a frame immediately.
+ ScheduleForcedGenerateFrame(wr::RenderReasons::WIDGET);
+ return true;
+}
+
+void WebRenderBridgeParent::ClearResources() {
+ if (!mApi) {
+ return;
+ }
+
+ if (!IsRootWebRenderBridgeParent()) {
+ mApi->FlushPendingWrTransactionEventsWithoutWait();
+ }
+
+ LOG("WebRenderBridgeParent::ClearResources() PipelineId %" PRIx64
+ " Id %" PRIx64 " root %d",
+ wr::AsUint64(mPipelineId), wr::AsUint64(mApi->GetId()),
+ IsRootWebRenderBridgeParent());
+
+ wr::Epoch wrEpoch = GetNextWrEpoch();
+ mReceivedDisplayList = false;
+ // Schedule generate frame to clean up Pipeline
+ ScheduleGenerateFrame(wr::RenderReasons::CLEAR_RESOURCES);
+
+ // WrFontKeys and WrImageKeys are deleted during WebRenderAPI destruction.
+ for (const auto& entry : mTextureHosts) {
+ WebRenderTextureHost* wrTexture = entry.second->AsWebRenderTextureHost();
+ MOZ_ASSERT(wrTexture);
+ if (wrTexture) {
+ mAsyncImageManager->HoldExternalImage(mPipelineId, wrEpoch, wrTexture);
+ }
+ }
+ mTextureHosts.clear();
+
+ for (const auto& entry : mSharedSurfaceIds) {
+ mAsyncImageManager->HoldExternalImage(mPipelineId, mWrEpoch, entry.second);
+ }
+ mSharedSurfaceIds.clear();
+
+ mAsyncImageManager->RemovePipeline(mPipelineId, wrEpoch);
+
+ wr::TransactionBuilder txn(mApi);
+ txn.SetLowPriority(true);
+ txn.ClearDisplayList(wrEpoch, mPipelineId);
+
+ for (const auto& entry : mAsyncCompositables) {
+ wr::PipelineId pipelineId = wr::AsPipelineId(entry.first);
+ RefPtr<WebRenderImageHost> host = entry.second;
+ host->ClearWrBridge(pipelineId, this);
+ mAsyncImageManager->RemoveAsyncImagePipeline(pipelineId, txn);
+ txn.RemovePipeline(pipelineId);
+ }
+ mAsyncCompositables.clear();
+ txn.RemovePipeline(mPipelineId);
+ mApi->SendTransaction(txn);
+
+ ClearAnimationResources();
+
+ if (IsRootWebRenderBridgeParent()) {
+ mCompositorScheduler->Destroy();
+ mApi->DestroyRenderer();
+ }
+
+ mCompositorScheduler = nullptr;
+ mAsyncImageManager = nullptr;
+ mApi = nullptr;
+ mCompositorBridge = nullptr;
+}
+
+void WebRenderBridgeParent::ClearAnimationResources() {
+ if (RefPtr<OMTASampler> sampler = GetOMTASampler()) {
+ sampler->ClearActiveAnimations(mActiveAnimations);
+ }
+ mActiveAnimations.clear();
+ std::queue<CompositorAnimationIdsForEpoch>().swap(
+ mCompositorAnimationsToDelete); // clear queue
+}
+
+void WebRenderBridgeParent::SendAsyncMessage(
+ const nsTArray<AsyncParentMessageData>& aMessage) {
+ MOZ_ASSERT_UNREACHABLE("unexpected to be called");
+}
+
+void WebRenderBridgeParent::SendPendingAsyncMessages() {
+ MOZ_ASSERT(mCompositorBridge);
+ mCompositorBridge->SendPendingAsyncMessages();
+}
+
+void WebRenderBridgeParent::SetAboutToSendAsyncMessages() {
+ MOZ_ASSERT(mCompositorBridge);
+ mCompositorBridge->SetAboutToSendAsyncMessages();
+}
+
+void WebRenderBridgeParent::NotifyNotUsed(PTextureParent* aTexture,
+ uint64_t aTransactionId) {
+ MOZ_ASSERT_UNREACHABLE("unexpected to be called");
+}
+
+base::ProcessId WebRenderBridgeParent::GetChildProcessId() {
+ return OtherPid();
+}
+
+dom::ContentParentId WebRenderBridgeParent::GetContentId() {
+ MOZ_ASSERT(mCompositorBridge);
+ return mCompositorBridge->GetContentId();
+}
+
+bool WebRenderBridgeParent::IsSameProcess() const {
+ return OtherPid() == base::GetCurrentProcId();
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvNewCompositable(
+ const CompositableHandle& aHandle, const TextureInfo& aInfo) {
+ if (mDestroyed) {
+ return IPC_OK();
+ }
+ if (!AddCompositable(aHandle, aInfo)) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvReleaseCompositable(
+ const CompositableHandle& aHandle) {
+ if (mDestroyed) {
+ return IPC_OK();
+ }
+ ReleaseCompositable(aHandle);
+ return IPC_OK();
+}
+
+TextureFactoryIdentifier WebRenderBridgeParent::GetTextureFactoryIdentifier() {
+ MOZ_ASSERT(mApi);
+
+#ifdef XP_WIN
+ const bool supportsD3D11NV12 = gfx::DeviceManagerDx::Get()->CanUseNV12();
+#else
+ const bool supportsD3D11NV12 = false;
+#endif
+
+ TextureFactoryIdentifier ident(
+ mApi->GetBackendType(), mApi->GetCompositorType(), XRE_GetProcessType(),
+ mApi->GetMaxTextureSize(), mApi->GetUseANGLE(), mApi->GetUseDComp(),
+ mAsyncImageManager->UseCompositorWnd(), false, false, false,
+ supportsD3D11NV12, mApi->GetSyncHandle());
+ return ident;
+}
+
+wr::Epoch WebRenderBridgeParent::GetNextWrEpoch() {
+ MOZ_RELEASE_ASSERT(mWrEpoch.mHandle != UINT32_MAX);
+ mWrEpoch.mHandle++;
+ return mWrEpoch;
+}
+
+void WebRenderBridgeParent::RollbackWrEpoch() {
+ MOZ_RELEASE_ASSERT(mWrEpoch.mHandle != 0);
+ mWrEpoch.mHandle--;
+}
+
+void WebRenderBridgeParent::ExtractImageCompositeNotifications(
+ nsTArray<ImageCompositeNotificationInfo>* aNotifications) {
+ MOZ_ASSERT(IsRootWebRenderBridgeParent());
+ if (mDestroyed) {
+ return;
+ }
+ mAsyncImageManager->FlushImageNotifications(aNotifications);
+}
+
+void WebRenderBridgeParent::FlushPendingWrTransactionEventsWithWait() {
+ if (mDestroyed || IsRootWebRenderBridgeParent()) {
+ return;
+ }
+ mApi->FlushPendingWrTransactionEventsWithWait();
+}
+
+RefPtr<WebRenderBridgeParentRef>
+WebRenderBridgeParent::GetWebRenderBridgeParentRef() {
+ if (mDestroyed) {
+ return nullptr;
+ }
+
+ if (!mWebRenderBridgeRef) {
+ mWebRenderBridgeRef = new WebRenderBridgeParentRef(this);
+ }
+ return mWebRenderBridgeRef;
+}
+
+WebRenderBridgeParentRef::WebRenderBridgeParentRef(
+ WebRenderBridgeParent* aWebRenderBridge)
+ : mWebRenderBridge(aWebRenderBridge) {
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ MOZ_ASSERT(mWebRenderBridge);
+}
+
+RefPtr<WebRenderBridgeParent> WebRenderBridgeParentRef::WrBridge() {
+ return mWebRenderBridge;
+}
+
+void WebRenderBridgeParentRef::Clear() {
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ mWebRenderBridge = nullptr;
+}
+
+WebRenderBridgeParentRef::~WebRenderBridgeParentRef() {
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ MOZ_ASSERT(!mWebRenderBridge);
+}
+
+} // namespace mozilla::layers
diff --git a/gfx/layers/wr/WebRenderBridgeParent.h b/gfx/layers/wr/WebRenderBridgeParent.h
new file mode 100644
index 0000000000..d8d80d1047
--- /dev/null
+++ b/gfx/layers/wr/WebRenderBridgeParent.h
@@ -0,0 +1,527 @@
+/* -*- 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/. */
+
+#ifndef mozilla_layers_WebRenderBridgeParent_h
+#define mozilla_layers_WebRenderBridgeParent_h
+
+#include <unordered_map>
+#include <unordered_set>
+
+#include "CompositableHost.h" // for CompositableHost, ImageCompositeNotificationInfo
+#include "GLContextProvider.h"
+#include "mozilla/DataMutex.h"
+#include "mozilla/layers/CompositableTransactionParent.h"
+#include "mozilla/layers/CompositorVsyncSchedulerOwner.h"
+#include "mozilla/layers/PWebRenderBridgeParent.h"
+#include "mozilla/HashTable.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Result.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/webrender/WebRenderTypes.h"
+#include "mozilla/webrender/WebRenderAPI.h"
+#include "nsTArrayForwardDeclare.h"
+#include "WindowRenderer.h"
+
+namespace mozilla {
+
+namespace gl {
+class GLContext;
+}
+
+namespace widget {
+class CompositorWidget;
+}
+
+namespace wr {
+class WebRenderAPI;
+class WebRenderPipelineInfo;
+} // namespace wr
+
+namespace layers {
+
+class AsyncImagePipelineManager;
+class Compositor;
+class CompositorBridgeParentBase;
+class CompositorVsyncScheduler;
+class OMTASampler;
+class RemoteTextureTxnScheduler;
+class UiCompositorControllerParent;
+class WebRenderBridgeParentRef;
+class WebRenderImageHost;
+struct WrAnimations;
+
+struct CompositorAnimationIdsForEpoch {
+ CompositorAnimationIdsForEpoch(const wr::Epoch& aEpoch,
+ nsTArray<uint64_t>&& aIds)
+ : mEpoch(aEpoch), mIds(std::move(aIds)) {}
+
+ wr::Epoch mEpoch;
+ nsTArray<uint64_t> mIds;
+};
+
+class WebRenderBridgeParent final : public PWebRenderBridgeParent,
+ public CompositorVsyncSchedulerOwner,
+ public CompositableParentManager,
+ public FrameRecorder {
+ public:
+ WebRenderBridgeParent(CompositorBridgeParentBase* aCompositorBridge,
+ const wr::PipelineId& aPipelineId,
+ widget::CompositorWidget* aWidget,
+ CompositorVsyncScheduler* aScheduler,
+ RefPtr<wr::WebRenderAPI>&& aApi,
+ RefPtr<AsyncImagePipelineManager>&& aImageMgr,
+ TimeDuration aVsyncRate);
+
+ static WebRenderBridgeParent* CreateDestroyed(
+ const wr::PipelineId& aPipelineId, nsCString&& aError);
+
+ wr::PipelineId PipelineId() { return mPipelineId; }
+ already_AddRefed<wr::WebRenderAPI> GetWebRenderAPI() {
+ return do_AddRef(mApi);
+ }
+ AsyncImagePipelineManager* AsyncImageManager() { return mAsyncImageManager; }
+ CompositorVsyncScheduler* CompositorScheduler() {
+ return mCompositorScheduler.get();
+ }
+ CompositorBridgeParentBase* GetCompositorBridge() {
+ return mCompositorBridge;
+ }
+
+ void UpdateQualitySettings();
+ void UpdateDebugFlags();
+ void UpdateParameters();
+ void UpdateBoolParameters();
+ void UpdateProfilerUI();
+
+ mozilla::ipc::IPCResult RecvEnsureConnected(
+ TextureFactoryIdentifier* aTextureFactoryIdentifier,
+ MaybeIdNamespace* aMaybeIdNamespace, nsCString* aError) override;
+
+ mozilla::ipc::IPCResult RecvNewCompositable(
+ const CompositableHandle& aHandle, const TextureInfo& aInfo) override;
+ mozilla::ipc::IPCResult RecvReleaseCompositable(
+ const CompositableHandle& aHandle) override;
+
+ mozilla::ipc::IPCResult RecvShutdown() override;
+ mozilla::ipc::IPCResult RecvShutdownSync() override;
+ mozilla::ipc::IPCResult RecvDeleteCompositorAnimations(
+ nsTArray<uint64_t>&& aIds) override;
+ mozilla::ipc::IPCResult RecvUpdateResources(
+ const wr::IdNamespace& aIdNamespace,
+ nsTArray<OpUpdateResource>&& aUpdates,
+ nsTArray<RefCountedShmem>&& aSmallShmems,
+ nsTArray<ipc::Shmem>&& aLargeShmems) override;
+ mozilla::ipc::IPCResult RecvSetDisplayList(
+ DisplayListData&& aDisplayList, nsTArray<OpDestroy>&& aToDestroy,
+ const uint64_t& aFwdTransactionId, const TransactionId& aTransactionId,
+ const bool& aContainsSVGGroup, const VsyncId& aVsyncId,
+ const TimeStamp& aVsyncStartTime, const TimeStamp& aRefreshStartTime,
+ const TimeStamp& aTxnStartTime, const nsACString& aTxnURL,
+ const TimeStamp& aFwdTime,
+ nsTArray<CompositionPayload>&& aPayloads) override;
+ mozilla::ipc::IPCResult RecvEmptyTransaction(
+ const FocusTarget& aFocusTarget,
+ Maybe<TransactionData>&& aTransactionData,
+ nsTArray<OpDestroy>&& aToDestroy, const uint64_t& aFwdTransactionId,
+ const TransactionId& aTransactionId, const VsyncId& aVsyncId,
+ const TimeStamp& aVsyncStartTime, const TimeStamp& aRefreshStartTime,
+ const TimeStamp& aTxnStartTime, const nsACString& aTxnURL,
+ const TimeStamp& aFwdTime,
+ nsTArray<CompositionPayload>&& aPayloads) override;
+ mozilla::ipc::IPCResult RecvSetFocusTarget(
+ const FocusTarget& aFocusTarget) override;
+ mozilla::ipc::IPCResult RecvParentCommands(
+ const wr::IdNamespace& aIdNamespace,
+ nsTArray<WebRenderParentCommand>&& commands) override;
+ mozilla::ipc::IPCResult RecvGetSnapshot(NotNull<PTextureParent*> aTexture,
+ bool* aNeedsYFlip) override;
+
+ mozilla::ipc::IPCResult RecvClearCachedResources() override;
+ mozilla::ipc::IPCResult RecvClearAnimationResources() override;
+ mozilla::ipc::IPCResult RecvInvalidateRenderedFrame() override;
+ mozilla::ipc::IPCResult RecvScheduleComposite(
+ const wr::RenderReasons& aReasons) override;
+ mozilla::ipc::IPCResult RecvCapture() override;
+ mozilla::ipc::IPCResult RecvStartCaptureSequence(
+ const nsACString& path, const uint32_t& aFlags) override;
+ mozilla::ipc::IPCResult RecvStopCaptureSequence() override;
+ mozilla::ipc::IPCResult RecvSyncWithCompositor() override;
+
+ mozilla::ipc::IPCResult RecvSetConfirmedTargetAPZC(
+ const uint64_t& aBlockId,
+ nsTArray<ScrollableLayerGuid>&& aTargets) override;
+
+ mozilla::ipc::IPCResult RecvSetTestSampleTime(
+ const TimeStamp& aTime) override;
+ mozilla::ipc::IPCResult RecvLeaveTestMode() override;
+ mozilla::ipc::IPCResult RecvGetAnimationValue(
+ const uint64_t& aCompositorAnimationsId, OMTAValue* aValue) override;
+ mozilla::ipc::IPCResult RecvSetAsyncScrollOffset(
+ const ScrollableLayerGuid::ViewID& aScrollId, const float& aX,
+ const float& aY) override;
+ mozilla::ipc::IPCResult RecvSetAsyncZoom(
+ const ScrollableLayerGuid::ViewID& aScrollId,
+ const float& aZoom) override;
+ mozilla::ipc::IPCResult RecvFlushApzRepaints() override;
+ mozilla::ipc::IPCResult RecvGetAPZTestData(APZTestData* data) override;
+ mozilla::ipc::IPCResult RecvGetFrameUniformity(
+ FrameUniformityData* aOutData) override;
+
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ mozilla::ipc::IPCResult RecvSetDefaultClearColor(
+ const uint32_t& aColor) override;
+ void SetClearColor(const gfx::DeviceColor& aColor);
+
+ void Pause();
+ bool Resume();
+
+ void Destroy();
+
+ // CompositorVsyncSchedulerOwner
+ bool IsPendingComposite() override { return false; }
+ void FinishPendingComposite() override {}
+ void CompositeToTarget(VsyncId aId, wr::RenderReasons aReasons,
+ gfx::DrawTarget* aTarget,
+ const gfx::IntRect* aRect = nullptr) override;
+ TimeDuration GetVsyncInterval() const override;
+
+ // CompositableParentManager
+ bool IsSameProcess() const override;
+ base::ProcessId GetChildProcessId() override;
+ dom::ContentParentId GetContentId() override;
+ void NotifyNotUsed(PTextureParent* aTexture,
+ uint64_t aTransactionId) override;
+ void SendAsyncMessage(
+ const nsTArray<AsyncParentMessageData>& aMessage) override;
+ void SendPendingAsyncMessages() override;
+ void SetAboutToSendAsyncMessages() override;
+
+ void HoldPendingTransactionId(
+ const wr::Epoch& aWrEpoch, TransactionId aTransactionId,
+ bool aContainsSVGGroup, const VsyncId& aVsyncId,
+ const TimeStamp& aVsyncStartTime, const TimeStamp& aRefreshStartTime,
+ const TimeStamp& aTxnStartTime, const nsACString& aTxnURL,
+ const TimeStamp& aFwdTime, const bool aIsFirstPaint,
+ nsTArray<CompositionPayload>&& aPayloads,
+ const bool aUseForTelemetry = true);
+ TransactionId LastPendingTransactionId();
+ void FlushTransactionIdsForEpoch(
+ const wr::Epoch& aEpoch, const VsyncId& aCompositeStartId,
+ const TimeStamp& aCompositeStartTime, const TimeStamp& aRenderStartTime,
+ const TimeStamp& aEndTime, UiCompositorControllerParent* aUiController,
+ wr::RendererStats* aStats, nsTArray<FrameStats>& aOutputStats,
+ nsTArray<TransactionId>& aOutputTransactions);
+ void NotifySceneBuiltForEpoch(const wr::Epoch& aEpoch,
+ const TimeStamp& aEndTime);
+
+ void RetrySkippedComposite();
+
+ TextureFactoryIdentifier GetTextureFactoryIdentifier();
+
+ void ExtractImageCompositeNotifications(
+ nsTArray<ImageCompositeNotificationInfo>* aNotifications);
+
+ wr::Epoch GetCurrentEpoch() const { return mWrEpoch; }
+ wr::IdNamespace GetIdNamespace() { return mIdNamespace; }
+
+ bool MatchesNamespace(const wr::ImageKey& aImageKey) const {
+ return aImageKey.mNamespace == mIdNamespace;
+ }
+
+ bool MatchesNamespace(const wr::BlobImageKey& aBlobKey) const {
+ return MatchesNamespace(wr::AsImageKey(aBlobKey));
+ }
+
+ bool MatchesNamespace(const wr::FontKey& aFontKey) const {
+ return aFontKey.mNamespace == mIdNamespace;
+ }
+
+ bool MatchesNamespace(const wr::FontInstanceKey& aFontKey) const {
+ return aFontKey.mNamespace == mIdNamespace;
+ }
+
+ void FlushRendering(wr::RenderReasons aReasons, bool aWaitForPresent = true);
+
+ /**
+ * Schedule generating WebRender frame definitely at next composite timing.
+ *
+ * WebRenderBridgeParent uses composite timing to check if there is an update
+ * to AsyncImagePipelines. If there is no update, WebRenderBridgeParent skips
+ * to generate frame. If we need to generate new frame at next composite
+ * timing, call this method.
+ *
+ * Call CompositorVsyncScheduler::ScheduleComposition() directly, if we just
+ * want to trigger AsyncImagePipelines update checks.
+ */
+ void ScheduleGenerateFrame(wr::RenderReasons aReason);
+
+ /**
+ * Invalidate rendered frame.
+ *
+ * WebRender could skip frame rendering if there is no update.
+ * This function is used to force invalidating even when there is no update.
+ */
+ void InvalidateRenderedFrame(wr::RenderReasons aReasons);
+
+ /**
+ * Schedule forced frame rendering at next composite timing.
+ *
+ * WebRender could skip frame rendering if there is no update.
+ * This function is used to force rendering even when there is no update.
+ */
+ void ScheduleForcedGenerateFrame(wr::RenderReasons aReasons);
+
+ void NotifyDidSceneBuild(RefPtr<const wr::WebRenderPipelineInfo> aInfo);
+
+ wr::Epoch UpdateWebRender(
+ CompositorVsyncScheduler* aScheduler, RefPtr<wr::WebRenderAPI>&& aApi,
+ AsyncImagePipelineManager* aImageMgr,
+ const TextureFactoryIdentifier& aTextureFactoryIdentifier);
+
+ void RemoveEpochDataPriorTo(const wr::Epoch& aRenderedEpoch);
+
+ bool IsRootWebRenderBridgeParent() const;
+ LayersId GetLayersId() const;
+
+ void BeginRecording(const TimeStamp& aRecordingStart);
+
+#if defined(MOZ_WIDGET_ANDROID)
+ /**
+ * Request a screengrab for android
+ */
+ void RequestScreenPixels(UiCompositorControllerParent* aController);
+ void MaybeCaptureScreenPixels();
+#endif
+ /**
+ * Stop recording and the frames collected since the call to BeginRecording
+ */
+ RefPtr<wr::WebRenderAPI::EndRecordingPromise> EndRecording();
+
+ void DisableNativeCompositor();
+ void AddPendingScrollPayload(CompositionPayload& aPayload,
+ const VsyncId& aCompositeStartId);
+
+ nsTArray<CompositionPayload> TakePendingScrollPayload(
+ const VsyncId& aCompositeStartId);
+
+ RefPtr<WebRenderBridgeParentRef> GetWebRenderBridgeParentRef();
+
+ void FlushPendingWrTransactionEventsWithWait();
+
+ private:
+ class ScheduleSharedSurfaceRelease;
+
+ WebRenderBridgeParent(const wr::PipelineId& aPipelineId, nsCString&& aError);
+ virtual ~WebRenderBridgeParent();
+
+ bool ProcessEmptyTransactionUpdates(TransactionData& aData,
+ bool* aScheduleComposite);
+
+ bool ProcessDisplayListData(DisplayListData& aDisplayList, wr::Epoch aWrEpoch,
+ const TimeStamp& aTxnStartTime,
+ bool aValidTransaction);
+
+ bool SetDisplayList(const LayoutDeviceRect& aRect, ipc::ByteBuf&& aDLItems,
+ ipc::ByteBuf&& aDLCache, ipc::ByteBuf&& aSpatialTreeDL,
+ const wr::BuiltDisplayListDescriptor& aDLDesc,
+ const nsTArray<OpUpdateResource>& aResourceUpdates,
+ const nsTArray<RefCountedShmem>& aSmallShmems,
+ const nsTArray<ipc::Shmem>& aLargeShmems,
+ const TimeStamp& aTxnStartTime,
+ wr::TransactionBuilder& aTxn, wr::Epoch aWrEpoch);
+
+ void UpdateAPZFocusState(const FocusTarget& aFocus);
+ void UpdateAPZScrollData(const wr::Epoch& aEpoch,
+ WebRenderScrollData&& aData);
+ void UpdateAPZScrollOffsets(ScrollUpdatesMap&& aUpdates,
+ uint32_t aPaintSequenceNumber);
+
+ bool UpdateResources(const nsTArray<OpUpdateResource>& aResourceUpdates,
+ const nsTArray<RefCountedShmem>& aSmallShmems,
+ const nsTArray<ipc::Shmem>& aLargeShmems,
+ wr::TransactionBuilder& aUpdates);
+ bool AddSharedExternalImage(wr::ExternalImageId aExtId, wr::ImageKey aKey,
+ wr::TransactionBuilder& aResources);
+ bool UpdateSharedExternalImage(
+ wr::ExternalImageId aExtId, wr::ImageKey aKey,
+ const ImageIntRect& aDirtyRect, wr::TransactionBuilder& aResources,
+ UniquePtr<ScheduleSharedSurfaceRelease>& aScheduleRelease);
+ void ObserveSharedSurfaceRelease(
+ const nsTArray<wr::ExternalImageKeyPair>& aPairs,
+ const bool& aFromCheckpoint);
+
+ bool PushExternalImageForTexture(wr::ExternalImageId aExtId,
+ wr::ImageKey aKey, TextureHost* aTexture,
+ bool aIsUpdate,
+ wr::TransactionBuilder& aResources);
+
+ void AddPipelineIdForCompositable(const wr::PipelineId& aPipelineIds,
+ const CompositableHandle& aHandle,
+ const CompositableHandleOwner& aOwner,
+ wr::TransactionBuilder& aTxn,
+ wr::TransactionBuilder& aTxnForImageBridge);
+ void RemovePipelineIdForCompositable(const wr::PipelineId& aPipelineId,
+ wr::TransactionBuilder& aTxn);
+
+ void DeleteImage(const wr::ImageKey& aKey, wr::TransactionBuilder& aUpdates);
+ void ReleaseTextureOfImage(const wr::ImageKey& aKey);
+
+ bool ProcessWebRenderParentCommands(
+ const nsTArray<WebRenderParentCommand>& aCommands,
+ wr::TransactionBuilder& aTxn);
+
+ void ClearResources();
+ void ClearAnimationResources();
+ mozilla::ipc::IPCResult HandleShutdown();
+
+ void MaybeNotifyOfLayers(wr::TransactionBuilder&, bool aWillHaveLayers);
+
+ void ResetPreviousSampleTime();
+
+ void SetOMTASampleTime();
+ RefPtr<OMTASampler> GetOMTASampler() const;
+
+ CompositorBridgeParent* GetRootCompositorBridgeParent() const;
+
+ RefPtr<WebRenderBridgeParent> GetRootWebRenderBridgeParent() const;
+
+ // Tell APZ what the subsequent sampling's timestamp should be.
+ void SetAPZSampleTime();
+
+ wr::Epoch GetNextWrEpoch();
+ // This function is expected to be used when GetNextWrEpoch() is called,
+ // but TransactionBuilder does not have resource updates nor display list.
+ // In this case, ScheduleGenerateFrame is not triggered via SceneBuilder.
+ // Then we want to rollback WrEpoch. See Bug 1490117.
+ void RollbackWrEpoch();
+
+ void FlushSceneBuilds();
+ void FlushFrameGeneration(wr::RenderReasons aReasons);
+ void FlushFramePresentation();
+
+ void MaybeGenerateFrame(VsyncId aId, bool aForceGenerateFrame,
+ wr::RenderReasons aReasons);
+
+ VsyncId GetVsyncIdForEpoch(const wr::Epoch& aEpoch) {
+ for (auto& id : mPendingTransactionIds) {
+ if (id.mEpoch.mHandle == aEpoch.mHandle) {
+ return id.mVsyncId;
+ }
+ }
+ return VsyncId();
+ }
+
+ private:
+ struct PendingTransactionId {
+ PendingTransactionId(const wr::Epoch& aEpoch, TransactionId aId,
+ bool aContainsSVGGroup, const VsyncId& aVsyncId,
+ const TimeStamp& aVsyncStartTime,
+ const TimeStamp& aRefreshStartTime,
+ const TimeStamp& aTxnStartTime,
+ const nsACString& aTxnURL, const TimeStamp& aFwdTime,
+ const bool aIsFirstPaint, const bool aUseForTelemetry,
+ nsTArray<CompositionPayload>&& aPayloads)
+ : mEpoch(aEpoch),
+ mId(aId),
+ mVsyncId(aVsyncId),
+ mVsyncStartTime(aVsyncStartTime),
+ mRefreshStartTime(aRefreshStartTime),
+ mTxnStartTime(aTxnStartTime),
+ mTxnURL(aTxnURL),
+ mFwdTime(aFwdTime),
+ mSkippedComposites(0),
+ mContainsSVGGroup(aContainsSVGGroup),
+ mIsFirstPaint(aIsFirstPaint),
+ mUseForTelemetry(aUseForTelemetry),
+ mPayloads(std::move(aPayloads)) {}
+ wr::Epoch mEpoch;
+ TransactionId mId;
+ VsyncId mVsyncId;
+ TimeStamp mVsyncStartTime;
+ TimeStamp mRefreshStartTime;
+ TimeStamp mTxnStartTime;
+ nsCString mTxnURL;
+ TimeStamp mFwdTime;
+ TimeStamp mSceneBuiltTime;
+ uint32_t mSkippedComposites;
+ bool mContainsSVGGroup;
+ bool mIsFirstPaint;
+ bool mUseForTelemetry;
+ nsTArray<CompositionPayload> mPayloads;
+ };
+
+ CompositorBridgeParentBase* MOZ_NON_OWNING_REF mCompositorBridge;
+ wr::PipelineId mPipelineId;
+ RefPtr<widget::CompositorWidget> mWidget;
+ RefPtr<wr::WebRenderAPI> mApi;
+ RefPtr<AsyncImagePipelineManager> mAsyncImageManager;
+ RefPtr<CompositorVsyncScheduler> mCompositorScheduler;
+ // mActiveAnimations is used to avoid leaking animations when
+ // WebRenderBridgeParent is destroyed abnormally and Tab move between
+ // different windows.
+ std::unordered_map<uint64_t, wr::Epoch> mActiveAnimations;
+ std::unordered_map<uint64_t, RefPtr<WebRenderImageHost>> mAsyncCompositables;
+ std::unordered_map<uint64_t, CompositableTextureHostRef> mTextureHosts;
+ std::unordered_map<uint64_t, wr::ExternalImageId> mSharedSurfaceIds;
+
+ TimeDuration mVsyncRate;
+ TimeStamp mPreviousFrameTimeStamp;
+
+ std::deque<PendingTransactionId> mPendingTransactionIds;
+ std::queue<CompositorAnimationIdsForEpoch> mCompositorAnimationsToDelete;
+ wr::Epoch mWrEpoch;
+ wr::IdNamespace mIdNamespace;
+ CompositionOpportunityId mCompositionOpportunityId;
+ nsCString mInitError;
+
+ TimeStamp mMostRecentComposite;
+
+ RefPtr<WebRenderBridgeParentRef> mWebRenderBridgeRef;
+
+#if defined(MOZ_WIDGET_ANDROID)
+ UiCompositorControllerParent* mScreenPixelsTarget;
+#endif
+ uint32_t mBoolParameterBits;
+ uint16_t mBlobTileSize;
+ wr::RenderReasons mSkippedCompositeReasons;
+ bool mDestroyed;
+ bool mIsFirstPaint;
+ bool mLastNotifiedHasLayers = false;
+ bool mReceivedDisplayList = false;
+ bool mSkippedComposite = false;
+ bool mDisablingNativeCompositor = false;
+ // These payloads are being used for SCROLL_PRESENT_LATENCY telemetry
+ DataMutex<nsClassHashtable<nsUint64HashKey, nsTArray<CompositionPayload>>>
+ mPendingScrollPayloads;
+
+ RefPtr<RemoteTextureTxnScheduler> mRemoteTextureTxnScheduler;
+};
+
+// Use this class, since WebRenderBridgeParent could not supports
+// ThreadSafeWeakPtr.
+// This class provides a ref of WebRenderBridgeParent when
+// the WebRenderBridgeParent is not destroyed. Then it works similar to
+// weak pointer.
+class WebRenderBridgeParentRef final {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WebRenderBridgeParentRef)
+
+ explicit WebRenderBridgeParentRef(WebRenderBridgeParent* aWebRenderBridge);
+
+ RefPtr<WebRenderBridgeParent> WrBridge();
+ void Clear();
+
+ protected:
+ ~WebRenderBridgeParentRef();
+
+ RefPtr<WebRenderBridgeParent> mWebRenderBridge;
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif // mozilla_layers_WebRenderBridgeParent_h
diff --git a/gfx/layers/wr/WebRenderCanvasRenderer.cpp b/gfx/layers/wr/WebRenderCanvasRenderer.cpp
new file mode 100644
index 0000000000..47dd08a7a4
--- /dev/null
+++ b/gfx/layers/wr/WebRenderCanvasRenderer.cpp
@@ -0,0 +1,94 @@
+/* -*- 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 "WebRenderCanvasRenderer.h"
+
+#include "GLContext.h"
+#include "GLScreenBuffer.h"
+#include "mozilla/layers/CompositorBridgeChild.h"
+#include "mozilla/StaticPrefs_webgl.h"
+#include "SharedSurfaceGL.h"
+#include "WebRenderBridgeChild.h"
+
+namespace mozilla {
+namespace layers {
+
+CompositableForwarder* WebRenderCanvasRenderer::GetForwarder() {
+ return mManager->WrBridge();
+}
+
+WebRenderCanvasRendererAsync::~WebRenderCanvasRendererAsync() {
+ if (mPipelineId.isSome()) {
+ mManager->RemovePipelineIdForCompositable(mPipelineId.ref());
+ mPipelineId.reset();
+ }
+}
+
+void WebRenderCanvasRendererAsync::Initialize(const CanvasRendererData& aData) {
+ WebRenderCanvasRenderer::Initialize(aData);
+
+ ClearCachedResources();
+}
+
+bool WebRenderCanvasRendererAsync::CreateCompositable() {
+ if (!mCanvasClient) {
+ auto compositableFlags = TextureFlags::NO_FLAGS;
+ if (!mData.mIsAlphaPremult) {
+ // WR needs this flag marked on the compositable, not just the texture.
+ compositableFlags |= TextureFlags::NON_PREMULTIPLIED;
+ }
+ mCanvasClient = new CanvasClient(GetForwarder(), compositableFlags);
+ mCanvasClient->Connect();
+ }
+ return true;
+}
+
+void WebRenderCanvasRendererAsync::EnsurePipeline() {
+ MOZ_ASSERT(mCanvasClient);
+ if (!mCanvasClient) {
+ return;
+ }
+
+ if (mPipelineId) {
+ return;
+ }
+
+ // Alloc async image pipeline id.
+ mPipelineId = Some(
+ mManager->WrBridge()->GetCompositorBridgeChild()->GetNextPipelineId());
+ mManager->AddPipelineIdForCompositable(
+ mPipelineId.ref(), mCanvasClient->GetIPCHandle(),
+ CompositableHandleOwner::WebRenderBridge);
+}
+
+bool WebRenderCanvasRendererAsync::HasPipeline() {
+ return mPipelineId.isSome();
+}
+
+void WebRenderCanvasRendererAsync::ClearCachedResources() {
+ if (mPipelineId.isSome()) {
+ mManager->RemovePipelineIdForCompositable(mPipelineId.ref());
+ mPipelineId.reset();
+ }
+}
+
+void WebRenderCanvasRendererAsync::
+ UpdateCompositableClientForEmptyTransaction() {
+ bool wasDirty = IsDirty();
+ UpdateCompositableClient();
+ if (wasDirty && mPipelineId.isSome()) {
+ // Notify an update of async image pipeline during empty transaction.
+ // During non empty transaction, WebRenderBridgeParent receives
+ // OpUpdateAsyncImagePipeline message, but during empty transaction, the
+ // message is not sent to WebRenderBridgeParent. Then
+ // OpUpdatedAsyncImagePipeline is used to notify the update.
+ mManager->AddWebRenderParentCommand(
+ OpUpdatedAsyncImagePipeline(mPipelineId.ref()));
+ }
+}
+
+} // namespace layers
+} // namespace mozilla
diff --git a/gfx/layers/wr/WebRenderCanvasRenderer.h b/gfx/layers/wr/WebRenderCanvasRenderer.h
new file mode 100644
index 0000000000..42e77b509c
--- /dev/null
+++ b/gfx/layers/wr/WebRenderCanvasRenderer.h
@@ -0,0 +1,57 @@
+/* -*- 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/. */
+
+#ifndef GFX_WEBRENDERCANVASRENDERER_H
+#define GFX_WEBRENDERCANVASRENDERER_H
+
+#include "mozilla/layers/RenderRootStateManager.h"
+#include "ShareableCanvasRenderer.h"
+
+namespace mozilla {
+namespace layers {
+
+class WebRenderCanvasRenderer : public ShareableCanvasRenderer {
+ public:
+ explicit WebRenderCanvasRenderer(RenderRootStateManager* aManager)
+ : mManager(aManager) {}
+
+ CompositableForwarder* GetForwarder() override;
+ RenderRootStateManager* GetRenderRootStateManager() { return mManager; }
+
+ protected:
+ RefPtr<RenderRootStateManager> mManager;
+};
+
+class WebRenderCanvasRendererAsync final : public WebRenderCanvasRenderer {
+ public:
+ explicit WebRenderCanvasRendererAsync(RenderRootStateManager* aManager)
+ : WebRenderCanvasRenderer(aManager) {}
+ virtual ~WebRenderCanvasRendererAsync();
+
+ WebRenderCanvasRendererAsync* AsWebRenderCanvasRendererAsync() override {
+ return this;
+ }
+
+ void Initialize(const CanvasRendererData& aData) override;
+ bool CreateCompositable() override;
+ void EnsurePipeline() override;
+ bool HasPipeline() override;
+
+ void ClearCachedResources() override;
+
+ void UpdateCompositableClientForEmptyTransaction();
+
+ Maybe<wr::PipelineId> GetPipelineId() { return mPipelineId; }
+
+ protected:
+ Maybe<wr::PipelineId> mPipelineId;
+ bool mIsAsync = false;
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif
diff --git a/gfx/layers/wr/WebRenderCommandBuilder.cpp b/gfx/layers/wr/WebRenderCommandBuilder.cpp
new file mode 100644
index 0000000000..e1bb2e1127
--- /dev/null
+++ b/gfx/layers/wr/WebRenderCommandBuilder.cpp
@@ -0,0 +1,2972 @@
+/* -*- 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 "WebRenderCommandBuilder.h"
+
+#include "mozilla/AutoRestore.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/EffectCompositor.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/StaticPrefs_gfx.h"
+#include "mozilla/SVGGeometryFrame.h"
+#include "mozilla/SVGImageFrame.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/gfx/Types.h"
+#include "mozilla/image/WebRenderImageProvider.h"
+#include "mozilla/layers/AnimationHelper.h"
+#include "mozilla/layers/ClipManager.h"
+#include "mozilla/layers/ImageClient.h"
+#include "mozilla/layers/RenderRootStateManager.h"
+#include "mozilla/layers/WebRenderBridgeChild.h"
+#include "mozilla/layers/WebRenderLayerManager.h"
+#include "mozilla/layers/IpcResourceUpdateQueue.h"
+#include "mozilla/layers/SharedSurfacesChild.h"
+#include "mozilla/layers/SourceSurfaceSharedData.h"
+#include "mozilla/layers/StackingContextHelper.h"
+#include "mozilla/layers/UpdateImageHelper.h"
+#include "mozilla/layers/WebRenderDrawEventRecorder.h"
+#include "UnitTransforms.h"
+#include "gfxEnv.h"
+#include "nsDisplayListInvalidation.h"
+#include "nsLayoutUtils.h"
+#include "nsTHashSet.h"
+#include "WebRenderCanvasRenderer.h"
+
+#include <cstdint>
+
+namespace mozilla {
+namespace layers {
+
+using namespace gfx;
+using namespace image;
+static int sIndent;
+#include <stdarg.h>
+#include <stdio.h>
+
+static void GP(const char* fmt, ...) {
+ va_list args;
+ va_start(args, fmt);
+#if 0
+ for (int i = 0; i < sIndent; i++) { printf(" "); }
+ vprintf(fmt, args);
+#endif
+ va_end(args);
+}
+
+bool FitsInt32(const float aVal) {
+ // Although int32_t min and max can't be represented exactly with floats, the
+ // cast truncates towards zero which is what we want here.
+ const float min = static_cast<float>(std::numeric_limits<int32_t>::min());
+ const float max = static_cast<float>(std::numeric_limits<int32_t>::max());
+ return aVal > min && aVal < max;
+}
+
+// XXX: problems:
+// - How do we deal with scrolling while having only a single invalidation rect?
+// We can have a valid rect and an invalid rect. As we scroll the valid rect
+// will move and the invalid rect will be the new area
+
+struct BlobItemData;
+static void DestroyBlobGroupDataProperty(nsTArray<BlobItemData*>* aArray);
+NS_DECLARE_FRAME_PROPERTY_WITH_DTOR(BlobGroupDataProperty,
+ nsTArray<BlobItemData*>,
+ DestroyBlobGroupDataProperty);
+
+// These are currently manually allocated and ownership is help by the
+// mDisplayItems hash table in DIGroup
+struct BlobItemData {
+ // a weak pointer to the frame for this item.
+ // DisplayItemData has a mFrameList to deal with merged frames. Hopefully we
+ // don't need to worry about that.
+ nsIFrame* mFrame;
+
+ uint32_t mDisplayItemKey;
+ nsTArray<BlobItemData*>*
+ mArray; // a weak pointer to the array that's owned by the frame property
+
+ LayerIntRect mRect;
+ // It would be nice to not need this. We need to be able to call
+ // ComputeInvalidationRegion. ComputeInvalidationRegion will sometimes reach
+ // into parent style structs to get information that can change the
+ // invalidation region
+ UniquePtr<nsDisplayItemGeometry> mGeometry;
+ DisplayItemClip mClip;
+ bool mInvisible;
+ bool mUsed; // initialized near construction
+ // XXX: only used for debugging
+ bool mInvalid;
+
+ // a weak pointer to the group that owns this item
+ // we use this to track whether group for a particular item has changed
+ struct DIGroup* mGroup;
+
+ // We need to keep a list of all the external surfaces used by the blob image.
+ // We do this on a per-display item basis so that the lists remains correct
+ // during invalidations.
+ DrawEventRecorderPrivate::ExternalSurfacesHolder mExternalSurfaces;
+
+ BlobItemData(DIGroup* aGroup, nsDisplayItem* aItem)
+ : mInvisible(false), mUsed(false), mGroup(aGroup) {
+ mInvalid = false;
+ mDisplayItemKey = aItem->GetPerFrameKey();
+ AddFrame(aItem->Frame());
+ }
+
+ private:
+ void AddFrame(nsIFrame* aFrame) {
+ mFrame = aFrame;
+
+ nsTArray<BlobItemData*>* array =
+ aFrame->GetProperty(BlobGroupDataProperty());
+ if (!array) {
+ array = new nsTArray<BlobItemData*>();
+ aFrame->SetProperty(BlobGroupDataProperty(), array);
+ }
+ array->AppendElement(this);
+ mArray = array;
+ }
+
+ public:
+ void ClearFrame() {
+ // Delete the weak pointer to this BlobItemData on the frame
+ MOZ_RELEASE_ASSERT(mFrame);
+ // the property may already be removed if WebRenderUserData got deleted
+ // first so we use our own mArray pointer.
+ mArray->RemoveElement(this);
+
+ // drop the entire property if nothing's left in the array
+ if (mArray->IsEmpty()) {
+ // If the frame is in the process of being destroyed this will fail
+ // but that's ok, because the the property will be removed then anyways
+ mFrame->RemoveProperty(BlobGroupDataProperty());
+ }
+ mFrame = nullptr;
+ }
+
+ ~BlobItemData() {
+ if (mFrame) {
+ ClearFrame();
+ }
+ }
+};
+
+static BlobItemData* GetBlobItemData(nsDisplayItem* aItem) {
+ nsIFrame* frame = aItem->Frame();
+ uint32_t key = aItem->GetPerFrameKey();
+ const nsTArray<BlobItemData*>* array =
+ frame->GetProperty(BlobGroupDataProperty());
+ if (array) {
+ for (BlobItemData* item : *array) {
+ if (item->mDisplayItemKey == key) {
+ return item;
+ }
+ }
+ }
+ return nullptr;
+}
+
+// We keep around the BlobItemData so that when we invalidate it get properly
+// included in the rect
+static void DestroyBlobGroupDataProperty(nsTArray<BlobItemData*>* aArray) {
+ for (BlobItemData* item : *aArray) {
+ GP("DestroyBlobGroupDataProperty: %p-%d\n", item->mFrame,
+ item->mDisplayItemKey);
+ item->mFrame = nullptr;
+ }
+ delete aArray;
+}
+
+static void TakeExternalSurfaces(
+ WebRenderDrawEventRecorder* aRecorder,
+ DrawEventRecorderPrivate::ExternalSurfacesHolder& aExternalSurfaces,
+ RenderRootStateManager* aManager, wr::IpcResourceUpdateQueue& aResources) {
+ aRecorder->TakeExternalSurfaces(aExternalSurfaces);
+
+ for (auto& entry : aExternalSurfaces) {
+ // While we don't use the image key with the surface, because the blob image
+ // renderer doesn't have easy access to the resource set, we still want to
+ // ensure one is generated. That will ensure the surface remains alive until
+ // at least the last epoch which the blob image could be used in.
+ wr::ImageKey key;
+ DebugOnly<nsresult> rv =
+ SharedSurfacesChild::Share(entry.mSurface, aManager, aResources, key);
+ MOZ_ASSERT(rv.value != NS_ERROR_NOT_IMPLEMENTED);
+ }
+}
+
+struct DIGroup;
+struct Grouper {
+ explicit Grouper(ClipManager& aClipManager)
+ : mAppUnitsPerDevPixel(0),
+ mDisplayListBuilder(nullptr),
+ mClipManager(aClipManager) {}
+
+ int32_t mAppUnitsPerDevPixel;
+ nsDisplayListBuilder* mDisplayListBuilder;
+ ClipManager& mClipManager;
+ HitTestInfoManager mHitTestInfoManager;
+ Matrix mTransform;
+
+ // Paint the list of aChildren display items.
+ void PaintContainerItem(DIGroup* aGroup, nsDisplayItem* aItem,
+ BlobItemData* aData, const IntRect& aItemBounds,
+ bool aDirty, nsDisplayList* aChildren,
+ gfxContext* aContext,
+ WebRenderDrawEventRecorder* aRecorder,
+ RenderRootStateManager* aRootManager,
+ wr::IpcResourceUpdateQueue& aResources);
+
+ // Builds groups of display items split based on 'layer activity'
+ void ConstructGroups(nsDisplayListBuilder* aDisplayListBuilder,
+ WebRenderCommandBuilder* aCommandBuilder,
+ wr::DisplayListBuilder& aBuilder,
+ wr::IpcResourceUpdateQueue& aResources, DIGroup* aGroup,
+ nsDisplayList* aList, nsDisplayItem* aWrappingItem,
+ const StackingContextHelper& aSc);
+ // Builds a group of display items without promoting anything to active.
+ bool ConstructGroupInsideInactive(WebRenderCommandBuilder* aCommandBuilder,
+ wr::DisplayListBuilder& aBuilder,
+ wr::IpcResourceUpdateQueue& aResources,
+ DIGroup* aGroup, nsDisplayList* aList,
+ const StackingContextHelper& aSc);
+ // Helper method for processing a single inactive item
+ bool ConstructItemInsideInactive(WebRenderCommandBuilder* aCommandBuilder,
+ wr::DisplayListBuilder& aBuilder,
+ wr::IpcResourceUpdateQueue& aResources,
+ DIGroup* aGroup, nsDisplayItem* aItem,
+ const StackingContextHelper& aSc,
+ bool* aOutIsInvisible);
+ ~Grouper() = default;
+};
+
+// Returns whether this is an item for which complete invalidation was
+// reliant on LayerTreeInvalidation in the pre-webrender world.
+static bool IsContainerLayerItem(nsDisplayItem* aItem) {
+ switch (aItem->GetType()) {
+ case DisplayItemType::TYPE_WRAP_LIST:
+ case DisplayItemType::TYPE_CONTAINER:
+ case DisplayItemType::TYPE_TRANSFORM:
+ case DisplayItemType::TYPE_OPACITY:
+ case DisplayItemType::TYPE_FILTER:
+ case DisplayItemType::TYPE_BLEND_CONTAINER:
+ case DisplayItemType::TYPE_BLEND_MODE:
+ case DisplayItemType::TYPE_MASK:
+ case DisplayItemType::TYPE_PERSPECTIVE: {
+ return true;
+ }
+ default: {
+ return false;
+ }
+ }
+}
+
+#include <sstream>
+
+static bool DetectContainerLayerPropertiesBoundsChange(
+ nsDisplayItem* aItem, BlobItemData* aData,
+ nsDisplayItemGeometry& aGeometry) {
+ if (aItem->GetType() == DisplayItemType::TYPE_FILTER) {
+ // Filters get clipped to the BuildingRect since they can
+ // have huge bounds outside of the visible area.
+ // This function and similar code in ComputeGeometryChange should be kept in
+ // sync.
+ aGeometry.mBounds = aGeometry.mBounds.Intersect(aItem->GetBuildingRect());
+ }
+
+ return !aGeometry.mBounds.IsEqualEdges(aData->mGeometry->mBounds);
+}
+
+/* A Display Item Group. This represents a set of diplay items that
+ * have been grouped together for rasterization and can be partially
+ * invalidated. It also tracks a number of properties from the environment
+ * that when changed would cause us to repaint like mScale. */
+struct DIGroup {
+ // XXX: Storing owning pointers to the BlobItemData in a hash table is not
+ // a good choice. There are two better options:
+ //
+ // 1. We should just be using a linked list for this stuff.
+ // That we can iterate over only the used items.
+ // We remove from the unused list and add to the used list
+ // when we see an item.
+ //
+ // we allocate using a free list.
+ //
+ // 2. We can use a Vec and use SwapRemove().
+ // We'll just need to be careful when iterating.
+ // The advantage of a Vec is that everything stays compact
+ // and we don't need to heap allocate the BlobItemData's
+ nsTHashSet<BlobItemData*> mDisplayItems;
+
+ LayerIntRect mInvalidRect;
+ LayerIntRect mVisibleRect;
+ // This is the last visible rect sent to WebRender. It's used
+ // to compute the invalid rect and ensure that we send
+ // the appropriate data to WebRender for merging.
+ LayerIntRect mLastVisibleRect;
+
+ // This is the intersection of mVisibleRect and mLastVisibleRect
+ // we ensure that mInvalidRect is contained in mPreservedRect
+ LayerIntRect mPreservedRect;
+ // mHitTestBounds is the same as mActualBounds except for the bounds
+ // of invisible items which are accounted for in the former but not
+ // in the latter.
+ LayerIntRect mHitTestBounds;
+ LayerIntRect mActualBounds;
+ int32_t mAppUnitsPerDevPixel;
+ gfx::MatrixScales mScale;
+ ScrollableLayerGuid::ViewID mScrollId;
+ CompositorHitTestInfo mHitInfo;
+ LayerPoint mResidualOffset;
+ LayerIntRect mLayerBounds; // mGroupBounds converted to Layer space
+ // mLayerBounds clipped to the container/parent of the
+ // current item being processed.
+ LayerIntRect mClippedImageBounds; // mLayerBounds with the clipping of any
+ // containers applied
+ Maybe<wr::BlobImageKey> mKey;
+ std::vector<RefPtr<ScaledFont>> mFonts;
+
+ DIGroup()
+ : mAppUnitsPerDevPixel(0),
+ mScrollId(ScrollableLayerGuid::NULL_SCROLL_ID),
+ mHitInfo(CompositorHitTestInvisibleToHit) {}
+
+ void InvalidateRect(const LayerIntRect& aRect) {
+ auto r = aRect.Intersect(mPreservedRect);
+ // Empty rects get dropped
+ if (!r.IsEmpty()) {
+ mInvalidRect = mInvalidRect.Union(r);
+ }
+ }
+
+ LayerIntRect ItemBounds(nsDisplayItem* aItem) {
+ BlobItemData* data = GetBlobItemData(aItem);
+ return data->mRect;
+ }
+
+ void ClearItems() {
+ GP("items: %d\n", mDisplayItems.Count());
+ for (BlobItemData* data : mDisplayItems) {
+ GP("Deleting %p-%d\n", data->mFrame, data->mDisplayItemKey);
+ delete data;
+ }
+ mDisplayItems.Clear();
+ }
+
+ void ClearImageKey(RenderRootStateManager* aManager, bool aForce = false) {
+ if (mKey) {
+ MOZ_RELEASE_ASSERT(aForce || mInvalidRect.IsEmpty());
+ aManager->AddBlobImageKeyForDiscard(*mKey);
+ mKey = Nothing();
+ }
+ mFonts.clear();
+ }
+
+ static LayerIntRect ToDeviceSpace(nsRect aBounds, Matrix& aMatrix,
+ int32_t aAppUnitsPerDevPixel) {
+ // RoundedOut can convert empty rectangles to non-empty ones
+ // so special case them here
+ if (aBounds.IsEmpty()) {
+ return LayerIntRect();
+ }
+ return LayerIntRect::FromUnknownRect(RoundedOut(aMatrix.TransformBounds(
+ ToRect(nsLayoutUtils::RectToGfxRect(aBounds, aAppUnitsPerDevPixel)))));
+ }
+
+ bool ComputeGeometryChange(nsDisplayItem* aItem, BlobItemData* aData,
+ Matrix& aMatrix, nsDisplayListBuilder* aBuilder) {
+ // If the frame is marked as invalidated, and didn't specify a rect to
+ // invalidate then we want to invalidate both the old and new bounds,
+ // otherwise we only want to invalidate the changed areas. If we do get an
+ // invalid rect, then we want to add this on top of the change areas.
+ nsRect invalid;
+ bool invalidated = false;
+ const DisplayItemClip& clip = aItem->GetClip();
+
+ int32_t appUnitsPerDevPixel =
+ aItem->Frame()->PresContext()->AppUnitsPerDevPixel();
+ MOZ_RELEASE_ASSERT(mAppUnitsPerDevPixel == appUnitsPerDevPixel);
+ GP("\n");
+ GP("clippedImageRect %d %d %d %d\n", mClippedImageBounds.x,
+ mClippedImageBounds.y, mClippedImageBounds.width,
+ mClippedImageBounds.height);
+ LayerIntSize size = mVisibleRect.Size();
+ GP("imageSize: %d %d\n", size.width, size.height);
+ /*if (aItem->IsReused() && aData->mGeometry) {
+ return;
+ }*/
+
+ GP("pre mInvalidRect: %s %p-%d - inv: %d %d %d %d\n", aItem->Name(),
+ aItem->Frame(), aItem->GetPerFrameKey(), mInvalidRect.x, mInvalidRect.y,
+ mInvalidRect.width, mInvalidRect.height);
+ if (!aData->mGeometry) {
+ // This item is being added for the first time, invalidate its entire
+ // area.
+ UniquePtr<nsDisplayItemGeometry> geometry(
+ aItem->AllocateGeometry(aBuilder));
+ nsRect clippedBounds = clip.ApplyNonRoundedIntersection(
+ geometry->ComputeInvalidationRegion());
+ aData->mGeometry = std::move(geometry);
+
+ LayerIntRect transformedRect =
+ ToDeviceSpace(clippedBounds, aMatrix, appUnitsPerDevPixel);
+ aData->mRect = transformedRect.Intersect(mClippedImageBounds);
+ GP("CGC %s %d %d %d %d\n", aItem->Name(), clippedBounds.x,
+ clippedBounds.y, clippedBounds.width, clippedBounds.height);
+ GP("%d %d, %f %f\n", mVisibleRect.TopLeft().x.value,
+ mVisibleRect.TopLeft().y.value, aMatrix._11, aMatrix._22);
+ GP("mRect %d %d %d %d\n", aData->mRect.x, aData->mRect.y,
+ aData->mRect.width, aData->mRect.height);
+ InvalidateRect(aData->mRect);
+ aData->mInvalid = true;
+ invalidated = true;
+ } else if (aItem->IsInvalid(invalid) && invalid.IsEmpty()) {
+ UniquePtr<nsDisplayItemGeometry> geometry(
+ aItem->AllocateGeometry(aBuilder));
+ nsRect clippedBounds = clip.ApplyNonRoundedIntersection(
+ geometry->ComputeInvalidationRegion());
+ aData->mGeometry = std::move(geometry);
+
+ GP("matrix: %f %f\n", aMatrix._31, aMatrix._32);
+ GP("frame invalid invalidate: %s\n", aItem->Name());
+ GP("old rect: %d %d %d %d\n", aData->mRect.x, aData->mRect.y,
+ aData->mRect.width, aData->mRect.height);
+ InvalidateRect(aData->mRect);
+ // We want to snap to outside pixels. When should we multiply by the
+ // matrix?
+ // XXX: TransformBounds is expensive. We should avoid doing it if we have
+ // no transform
+ LayerIntRect transformedRect =
+ ToDeviceSpace(clippedBounds, aMatrix, appUnitsPerDevPixel);
+ aData->mRect = transformedRect.Intersect(mClippedImageBounds);
+ InvalidateRect(aData->mRect);
+ GP("new rect: %d %d %d %d\n", aData->mRect.x, aData->mRect.y,
+ aData->mRect.width, aData->mRect.height);
+ aData->mInvalid = true;
+ invalidated = true;
+ } else {
+ GP("else invalidate: %s\n", aItem->Name());
+ nsRegion combined;
+ // this includes situations like reflow changing the position
+ aItem->ComputeInvalidationRegion(aBuilder, aData->mGeometry.get(),
+ &combined);
+ if (!combined.IsEmpty()) {
+ // There might be no point in doing this elaborate tracking here to get
+ // smaller areas
+ InvalidateRect(aData->mRect); // invalidate the old area -- in theory
+ // combined should take care of this
+ UniquePtr<nsDisplayItemGeometry> geometry(
+ aItem->AllocateGeometry(aBuilder));
+ // invalidate the invalidated area.
+
+ aData->mGeometry = std::move(geometry);
+
+ nsRect clippedBounds = clip.ApplyNonRoundedIntersection(
+ aData->mGeometry->ComputeInvalidationRegion());
+ LayerIntRect transformedRect =
+ ToDeviceSpace(clippedBounds, aMatrix, appUnitsPerDevPixel);
+ aData->mRect = transformedRect.Intersect(mClippedImageBounds);
+ InvalidateRect(aData->mRect);
+
+ aData->mInvalid = true;
+ invalidated = true;
+ } else {
+ if (aData->mClip != clip) {
+ UniquePtr<nsDisplayItemGeometry> geometry(
+ aItem->AllocateGeometry(aBuilder));
+ if (!IsContainerLayerItem(aItem)) {
+ // the bounds of layer items can change on us without
+ // ComputeInvalidationRegion returning any change. Other items
+ // shouldn't have any hidden geometry change.
+ MOZ_RELEASE_ASSERT(
+ geometry->mBounds.IsEqualEdges(aData->mGeometry->mBounds));
+ } else {
+ aData->mGeometry = std::move(geometry);
+ }
+ nsRect clippedBounds = clip.ApplyNonRoundedIntersection(
+ aData->mGeometry->ComputeInvalidationRegion());
+ LayerIntRect transformedRect =
+ ToDeviceSpace(clippedBounds, aMatrix, appUnitsPerDevPixel);
+ InvalidateRect(aData->mRect);
+ aData->mRect = transformedRect.Intersect(mClippedImageBounds);
+ InvalidateRect(aData->mRect);
+ invalidated = true;
+
+ GP("ClipChange: %s %d %d %d %d\n", aItem->Name(), aData->mRect.x,
+ aData->mRect.y, aData->mRect.XMost(), aData->mRect.YMost());
+
+ } else if (IsContainerLayerItem(aItem)) {
+ UniquePtr<nsDisplayItemGeometry> geometry(
+ aItem->AllocateGeometry(aBuilder));
+ // we need to catch bounds changes of containers so that we continue
+ // to have the correct bounds rects in the recording
+ if (DetectContainerLayerPropertiesBoundsChange(aItem, aData,
+ *geometry)) {
+ nsRect clippedBounds = clip.ApplyNonRoundedIntersection(
+ geometry->ComputeInvalidationRegion());
+ aData->mGeometry = std::move(geometry);
+ LayerIntRect transformedRect =
+ ToDeviceSpace(clippedBounds, aMatrix, appUnitsPerDevPixel);
+ InvalidateRect(aData->mRect);
+ aData->mRect = transformedRect.Intersect(mClippedImageBounds);
+ InvalidateRect(aData->mRect);
+ invalidated = true;
+ GP("DetectContainerLayerPropertiesBoundsChange change\n");
+ } else {
+ // Handle changes in mClippedImageBounds
+ nsRect clippedBounds = clip.ApplyNonRoundedIntersection(
+ geometry->ComputeInvalidationRegion());
+ LayerIntRect transformedRect =
+ ToDeviceSpace(clippedBounds, aMatrix, appUnitsPerDevPixel);
+ auto rect = transformedRect.Intersect(mClippedImageBounds);
+ if (!rect.IsEqualEdges(aData->mRect)) {
+ GP("ContainerLayer image rect bounds change\n");
+ InvalidateRect(aData->mRect);
+ aData->mRect = rect;
+ InvalidateRect(aData->mRect);
+ invalidated = true;
+ } else {
+ GP("Layer NoChange: %s %d %d %d %d\n", aItem->Name(),
+ aData->mRect.x, aData->mRect.y, aData->mRect.XMost(),
+ aData->mRect.YMost());
+ }
+ }
+ } else {
+ UniquePtr<nsDisplayItemGeometry> geometry(
+ aItem->AllocateGeometry(aBuilder));
+ nsRect clippedBounds = clip.ApplyNonRoundedIntersection(
+ geometry->ComputeInvalidationRegion());
+ LayerIntRect transformedRect =
+ ToDeviceSpace(clippedBounds, aMatrix, appUnitsPerDevPixel);
+ auto rect = transformedRect.Intersect(mClippedImageBounds);
+ // Make sure we update mRect for mClippedImageBounds changes
+ if (!rect.IsEqualEdges(aData->mRect)) {
+ GP("ContainerLayer image rect bounds change\n");
+ InvalidateRect(aData->mRect);
+ aData->mRect = rect;
+ InvalidateRect(aData->mRect);
+ invalidated = true;
+ } else {
+ GP("NoChange: %s %d %d %d %d\n", aItem->Name(), aData->mRect.x,
+ aData->mRect.y, aData->mRect.XMost(), aData->mRect.YMost());
+ }
+ }
+ }
+ }
+
+ if (aData->mGeometry && aItem->GetType() == DisplayItemType::TYPE_FILTER) {
+ // This hunk DetectContainerLayerPropertiesBoundsChange should be kept in
+ // sync.
+ aData->mGeometry->mBounds =
+ aData->mGeometry->mBounds.Intersect(aItem->GetBuildingRect());
+ }
+
+ mHitTestBounds.OrWith(aData->mRect);
+ if (!aData->mInvisible) {
+ mActualBounds.OrWith(aData->mRect);
+ }
+ aData->mClip = clip;
+ GP("post mInvalidRect: %d %d %d %d\n", mInvalidRect.x, mInvalidRect.y,
+ mInvalidRect.width, mInvalidRect.height);
+ return invalidated;
+ }
+
+ void EndGroup(WebRenderLayerManager* aWrManager,
+ nsDisplayListBuilder* aDisplayListBuilder,
+ wr::DisplayListBuilder& aBuilder,
+ wr::IpcResourceUpdateQueue& aResources, Grouper* aGrouper,
+ nsDisplayList::iterator aStartItem,
+ nsDisplayList::iterator aEndItem) {
+ GP("\n\n");
+ GP("Begin EndGroup\n");
+
+ auto scale = LayoutDeviceToLayerScale2D::FromUnknownScale(mScale);
+
+ auto hitTestRect = mVisibleRect.Intersect(ViewAs<LayerPixel>(
+ mHitTestBounds, PixelCastJustification::LayerIsImage));
+ if (!hitTestRect.IsEmpty()) {
+ auto deviceHitTestRect =
+ (LayerRect(hitTestRect) - mResidualOffset) / scale;
+ PushHitTest(aBuilder, deviceHitTestRect);
+ }
+
+ mVisibleRect = mVisibleRect.Intersect(ViewAs<LayerPixel>(
+ mActualBounds, PixelCastJustification::LayerIsImage));
+
+ if (mVisibleRect.IsEmpty()) {
+ return;
+ }
+
+ // Invalidate any unused items
+ GP("mDisplayItems\n");
+ mDisplayItems.RemoveIf([&](BlobItemData* data) {
+ GP(" : %p-%d\n", data->mFrame, data->mDisplayItemKey);
+ if (!data->mUsed) {
+ GP("Invalidate unused: %p-%d\n", data->mFrame, data->mDisplayItemKey);
+ InvalidateRect(data->mRect);
+ delete data;
+ return true;
+ }
+
+ data->mUsed = false;
+ return false;
+ });
+
+ IntSize dtSize = mVisibleRect.Size().ToUnknownSize();
+ // The actual display item's size shouldn't have the scale factored in
+ // Round the bounds out to leave space for unsnapped content
+ LayoutDeviceRect itemBounds =
+ (LayerRect(mVisibleRect) - mResidualOffset) / scale;
+
+ if (mInvalidRect.IsEmpty() && mVisibleRect.IsEqualEdges(mLastVisibleRect)) {
+ GP("Not repainting group because it's empty\n");
+ GP("End EndGroup\n");
+ if (mKey) {
+ // Although the contents haven't changed, the visible area *may* have,
+ // so request it be updated unconditionally (wr should be able to easily
+ // detect if this is a no-op on its side, if that matters)
+ aResources.SetBlobImageVisibleArea(
+ *mKey, ViewAs<ImagePixel>(mVisibleRect,
+ PixelCastJustification::LayerIsImage));
+ mLastVisibleRect = mVisibleRect;
+ PushImage(aBuilder, itemBounds);
+ }
+ return;
+ }
+
+ std::vector<RefPtr<ScaledFont>> fonts;
+ bool validFonts = true;
+ RefPtr<WebRenderDrawEventRecorder> recorder =
+ MakeAndAddRef<WebRenderDrawEventRecorder>(
+ [&](MemStream& aStream,
+ std::vector<RefPtr<ScaledFont>>& aScaledFonts) {
+ size_t count = aScaledFonts.size();
+ aStream.write((const char*)&count, sizeof(count));
+ for (auto& scaled : aScaledFonts) {
+ Maybe<wr::FontInstanceKey> key =
+ aWrManager->WrBridge()->GetFontKeyForScaledFont(scaled,
+ aResources);
+ if (key.isNothing()) {
+ validFonts = false;
+ break;
+ }
+ BlobFont font = {key.value(), scaled};
+ aStream.write((const char*)&font, sizeof(font));
+ }
+ fonts = std::move(aScaledFonts);
+ });
+
+ RefPtr<gfx::DrawTarget> dummyDt =
+ gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
+
+ RefPtr<gfx::DrawTarget> dt = gfx::Factory::CreateRecordingDrawTarget(
+ recorder, dummyDt, mLayerBounds.ToUnknownRect());
+ if (!dt || !dt->IsValid()) {
+ gfxCriticalNote << "Failed to create drawTarget for blob image";
+ return;
+ }
+
+ gfxContext context(dt);
+ context.SetMatrix(Matrix::Scaling(mScale).PostTranslate(mResidualOffset.x,
+ mResidualOffset.y));
+
+ GP("mInvalidRect: %d %d %d %d\n", mInvalidRect.x, mInvalidRect.y,
+ mInvalidRect.width, mInvalidRect.height);
+
+ RenderRootStateManager* rootManager =
+ aWrManager->GetRenderRootStateManager();
+
+ bool empty = aStartItem == aEndItem;
+ if (empty) {
+ ClearImageKey(rootManager, true);
+ return;
+ }
+
+ PaintItemRange(aGrouper, aStartItem, aEndItem, &context, recorder,
+ rootManager, aResources);
+
+ // XXX: set this correctly perhaps using
+ // aItem->GetOpaqueRegion(aDisplayListBuilder, &snapped).
+ // Contains(paintBounds);?
+ wr::OpacityType opacity = wr::OpacityType::HasAlphaChannel;
+
+ bool hasItems = recorder->Finish();
+ GP("%d Finish\n", hasItems);
+ if (!validFonts) {
+ gfxCriticalNote << "Failed serializing fonts for blob image";
+ return;
+ }
+ Range<uint8_t> bytes((uint8_t*)recorder->mOutputStream.mData,
+ recorder->mOutputStream.mLength);
+ if (!mKey) {
+ // we don't want to send a new image that doesn't have any
+ // items in it
+ if (!hasItems || mVisibleRect.IsEmpty()) {
+ GP("Skipped group with no items\n");
+ return;
+ }
+
+ wr::BlobImageKey key =
+ wr::BlobImageKey{aWrManager->WrBridge()->GetNextImageKey()};
+ GP("No previous key making new one %d\n", key._0.mHandle);
+ wr::ImageDescriptor descriptor(dtSize, 0, dt->GetFormat(), opacity);
+ MOZ_RELEASE_ASSERT(bytes.length() > sizeof(size_t));
+ if (!aResources.AddBlobImage(
+ key, descriptor, bytes,
+ ViewAs<ImagePixel>(mVisibleRect,
+ PixelCastJustification::LayerIsImage))) {
+ return;
+ }
+ mKey = Some(key);
+ } else {
+ MOZ_DIAGNOSTIC_ASSERT(
+ aWrManager->WrBridge()->MatchesNamespace(mKey.ref()),
+ "Stale blob key for group!");
+
+ wr::ImageDescriptor descriptor(dtSize, 0, dt->GetFormat(), opacity);
+
+ // Convert mInvalidRect to image space by subtracting the corner of the
+ // image bounds
+ auto dirtyRect = ViewAs<ImagePixel>(mInvalidRect,
+ PixelCastJustification::LayerIsImage);
+
+ auto bottomRight = dirtyRect.BottomRight();
+ GP("check invalid %d %d - %d %d\n", bottomRight.x.value,
+ bottomRight.y.value, dtSize.width, dtSize.height);
+ GP("Update Blob %d %d %d %d\n", mInvalidRect.x, mInvalidRect.y,
+ mInvalidRect.width, mInvalidRect.height);
+ if (!aResources.UpdateBlobImage(
+ *mKey, descriptor, bytes,
+ ViewAs<ImagePixel>(mVisibleRect,
+ PixelCastJustification::LayerIsImage),
+ dirtyRect)) {
+ return;
+ }
+ }
+ mFonts = std::move(fonts);
+ aResources.SetBlobImageVisibleArea(
+ *mKey,
+ ViewAs<ImagePixel>(mVisibleRect, PixelCastJustification::LayerIsImage));
+ mLastVisibleRect = mVisibleRect;
+ PushImage(aBuilder, itemBounds);
+ GP("End EndGroup\n\n");
+ }
+
+ void PushImage(wr::DisplayListBuilder& aBuilder,
+ const LayoutDeviceRect& bounds) {
+ wr::LayoutRect dest = wr::ToLayoutRect(bounds);
+ GP("PushImage: %f %f %f %f\n", dest.min.x, dest.min.y, dest.max.x,
+ dest.max.y);
+ // wr::ToImageRendering(aItem->Frame()->UsedImageRendering());
+ auto rendering = wr::ImageRendering::Auto;
+ bool backfaceHidden = false;
+
+ // XXX - clipping the item against the paint rect breaks some content.
+ // cf. Bug 1455422.
+ // wr::LayoutRect clip = wr::ToLayoutRect(bounds.Intersect(mVisibleRect));
+
+ aBuilder.PushImage(dest, dest, !backfaceHidden, false, rendering,
+ wr::AsImageKey(*mKey));
+ }
+
+ void PushHitTest(wr::DisplayListBuilder& aBuilder,
+ const LayoutDeviceRect& bounds) {
+ wr::LayoutRect dest = wr::ToLayoutRect(bounds);
+ GP("PushHitTest: %f %f %f %f\n", dest.min.x, dest.min.y, dest.max.x,
+ dest.max.y);
+
+ // We don't really know the exact shape of this blob because it may contain
+ // SVG shapes. Also mHitInfo may be a combination of hit info flags from
+ // different shapes so generate an irregular-area hit-test region for it.
+ CompositorHitTestInfo hitInfo = mHitInfo;
+ if (hitInfo.contains(CompositorHitTestFlags::eVisibleToHitTest)) {
+ hitInfo += CompositorHitTestFlags::eIrregularArea;
+ }
+
+ bool backfaceHidden = false;
+ aBuilder.PushHitTest(dest, dest, !backfaceHidden, mScrollId, hitInfo,
+ SideBits::eNone);
+ }
+
+ void PaintItemRange(Grouper* aGrouper, nsDisplayList::iterator aStartItem,
+ nsDisplayList::iterator aEndItem, gfxContext* aContext,
+ WebRenderDrawEventRecorder* aRecorder,
+ RenderRootStateManager* aRootManager,
+ wr::IpcResourceUpdateQueue& aResources) {
+ LayerIntSize size = mVisibleRect.Size();
+ for (auto it = aStartItem; it != aEndItem; ++it) {
+ nsDisplayItem* item = *it;
+ MOZ_ASSERT(item);
+
+ if (item->GetType() == DisplayItemType::TYPE_COMPOSITOR_HITTEST_INFO) {
+ continue;
+ }
+
+ BlobItemData* data = GetBlobItemData(item);
+ if (data->mInvisible) {
+ continue;
+ }
+
+ LayerIntRect bounds = data->mRect;
+
+ // skip empty items
+ if (bounds.IsEmpty()) {
+ continue;
+ }
+
+ GP("Trying %s %p-%d %d %d %d %d\n", item->Name(), item->Frame(),
+ item->GetPerFrameKey(), bounds.x, bounds.y, bounds.XMost(),
+ bounds.YMost());
+
+ auto bottomRight = bounds.BottomRight();
+
+ GP("paint check invalid %d %d - %d %d\n", bottomRight.x.value,
+ bottomRight.y.value, size.width, size.height);
+
+ bool dirty = true;
+ auto preservedBounds = bounds.Intersect(mPreservedRect);
+ if (!mInvalidRect.Contains(preservedBounds)) {
+ GP("Passing\n");
+ dirty = false;
+ if (data->mInvalid) {
+ gfxCriticalError()
+ << "DisplayItem" << item->Name() << "-should be invalid";
+ }
+ // if the item is invalid it needs to be fully contained
+ MOZ_RELEASE_ASSERT(!data->mInvalid);
+ }
+
+ nsDisplayList* children = item->GetChildren();
+ if (children) {
+ // If we aren't dirty, we still need to iterate over the children to
+ // ensure the blob index data is recorded the same as before to allow
+ // the merging of the parts inside in the invalid rect. Any items that
+ // are painted as a single item need to avoid repainting in that case.
+ GP("doing children in EndGroup\n");
+ aGrouper->PaintContainerItem(this, item, data, bounds.ToUnknownRect(),
+ dirty, children, aContext, aRecorder,
+ aRootManager, aResources);
+ continue;
+ }
+ nsPaintedDisplayItem* paintedItem = item->AsPaintedDisplayItem();
+ if (!paintedItem) {
+ continue;
+ }
+ if (dirty) {
+ // What should the clip settting strategy be? We can set the full
+ // clip everytime. this is probably easiest for now. An alternative
+ // would be to put the push and the pop into separate items and let
+ // invalidation handle it that way.
+ DisplayItemClip currentClip = paintedItem->GetClip();
+
+ if (currentClip.HasClip()) {
+ aContext->Save();
+ currentClip.ApplyTo(aContext, aGrouper->mAppUnitsPerDevPixel);
+ }
+ aContext->NewPath();
+ GP("painting %s %p-%d\n", paintedItem->Name(), paintedItem->Frame(),
+ paintedItem->GetPerFrameKey());
+ if (aGrouper->mDisplayListBuilder->IsPaintingToWindow()) {
+ paintedItem->Frame()->AddStateBits(NS_FRAME_PAINTED_THEBES);
+ }
+
+ paintedItem->Paint(aGrouper->mDisplayListBuilder, aContext);
+ TakeExternalSurfaces(aRecorder, data->mExternalSurfaces, aRootManager,
+ aResources);
+
+ if (currentClip.HasClip()) {
+ aContext->Restore();
+ }
+ }
+ aContext->GetDrawTarget()->FlushItem(bounds.ToUnknownRect());
+ }
+ }
+
+ ~DIGroup() {
+ GP("Group destruct\n");
+ for (BlobItemData* data : mDisplayItems) {
+ GP("Deleting %p-%d\n", data->mFrame, data->mDisplayItemKey);
+ delete data;
+ }
+ }
+};
+
+// If we have an item we need to make sure it matches the current group
+// otherwise it means the item switched groups and we need to invalidate
+// it and recreate the data.
+static BlobItemData* GetBlobItemDataForGroup(nsDisplayItem* aItem,
+ DIGroup* aGroup) {
+ BlobItemData* data = GetBlobItemData(aItem);
+ if (data) {
+ MOZ_ASSERT(data->mGroup->mDisplayItems.Contains(data));
+ if (data->mGroup != aGroup) {
+ GP("group don't match %p %p\n", data->mGroup, aGroup);
+ data->ClearFrame();
+ // the item is for another group
+ // it should be cleared out as being unused at the end of this paint
+ data = nullptr;
+ }
+ }
+ if (!data) {
+ GP("Allocating blob data\n");
+ data = new BlobItemData(aGroup, aItem);
+ aGroup->mDisplayItems.Insert(data);
+ }
+ data->mUsed = true;
+ return data;
+}
+
+void Grouper::PaintContainerItem(DIGroup* aGroup, nsDisplayItem* aItem,
+ BlobItemData* aData,
+ const IntRect& aItemBounds, bool aDirty,
+ nsDisplayList* aChildren, gfxContext* aContext,
+ WebRenderDrawEventRecorder* aRecorder,
+ RenderRootStateManager* aRootManager,
+ wr::IpcResourceUpdateQueue& aResources) {
+ switch (aItem->GetType()) {
+ case DisplayItemType::TYPE_TRANSFORM: {
+ DisplayItemClip currentClip = aItem->GetClip();
+
+ gfxContextMatrixAutoSaveRestore saveMatrix;
+ if (currentClip.HasClip()) {
+ aContext->Save();
+ currentClip.ApplyTo(aContext, this->mAppUnitsPerDevPixel);
+ aContext->GetDrawTarget()->FlushItem(aItemBounds);
+ } else {
+ saveMatrix.SetContext(aContext);
+ }
+
+ auto transformItem = static_cast<nsDisplayTransform*>(aItem);
+ Matrix4x4Flagged trans = transformItem->GetTransform();
+ Matrix trans2d;
+ if (!trans.Is2D(&trans2d)) {
+ // Painting will cause us to include the item's recording in the blob.
+ // We only want to do that if it is dirty, because otherwise the
+ // recording might change (e.g. due to factor of 2 scaling of images
+ // giving different results) and the merging will discard it because it
+ // is outside the invalid rect.
+ if (aDirty) {
+ // We don't currently support doing invalidation inside 3d transforms.
+ // For now just paint it as a single item.
+ aItem->AsPaintedDisplayItem()->Paint(mDisplayListBuilder, aContext);
+ TakeExternalSurfaces(aRecorder, aData->mExternalSurfaces,
+ aRootManager, aResources);
+ }
+ aContext->GetDrawTarget()->FlushItem(aItemBounds);
+ } else if (!trans2d.IsSingular()) {
+ aContext->Multiply(ThebesMatrix(trans2d));
+ aGroup->PaintItemRange(this, aChildren->begin(), aChildren->end(),
+ aContext, aRecorder, aRootManager, aResources);
+ }
+
+ if (currentClip.HasClip()) {
+ aContext->Restore();
+ aContext->GetDrawTarget()->FlushItem(aItemBounds);
+ }
+ break;
+ }
+ case DisplayItemType::TYPE_OPACITY: {
+ auto opacityItem = static_cast<nsDisplayOpacity*>(aItem);
+ float opacity = opacityItem->GetOpacity();
+ if (opacity == 0.0f) {
+ return;
+ }
+
+ aContext->GetDrawTarget()->PushLayer(false, opacityItem->GetOpacity(),
+ nullptr, mozilla::gfx::Matrix(),
+ aItemBounds);
+ GP("beginGroup %s %p-%d\n", aItem->Name(), aItem->Frame(),
+ aItem->GetPerFrameKey());
+ aContext->GetDrawTarget()->FlushItem(aItemBounds);
+ aGroup->PaintItemRange(this, aChildren->begin(), aChildren->end(),
+ aContext, aRecorder, aRootManager, aResources);
+ aContext->GetDrawTarget()->PopLayer();
+ GP("endGroup %s %p-%d\n", aItem->Name(), aItem->Frame(),
+ aItem->GetPerFrameKey());
+ aContext->GetDrawTarget()->FlushItem(aItemBounds);
+ break;
+ }
+ case DisplayItemType::TYPE_BLEND_MODE: {
+ auto blendItem = static_cast<nsDisplayBlendMode*>(aItem);
+ auto blendMode = blendItem->BlendMode();
+ aContext->GetDrawTarget()->PushLayerWithBlend(
+ false, 1.0, nullptr, mozilla::gfx::Matrix(), aItemBounds, false,
+ blendMode);
+ GP("beginGroup %s %p-%d\n", aItem->Name(), aItem->Frame(),
+ aItem->GetPerFrameKey());
+ aContext->GetDrawTarget()->FlushItem(aItemBounds);
+ aGroup->PaintItemRange(this, aChildren->begin(), aChildren->end(),
+ aContext, aRecorder, aRootManager, aResources);
+ aContext->GetDrawTarget()->PopLayer();
+ GP("endGroup %s %p-%d\n", aItem->Name(), aItem->Frame(),
+ aItem->GetPerFrameKey());
+ aContext->GetDrawTarget()->FlushItem(aItemBounds);
+ break;
+ }
+ case DisplayItemType::TYPE_BLEND_CONTAINER: {
+ aContext->GetDrawTarget()->PushLayer(false, 1.0, nullptr,
+ mozilla::gfx::Matrix(), aItemBounds);
+ GP("beginGroup %s %p-%d\n", aItem->Name(), aItem->Frame(),
+ aItem->GetPerFrameKey());
+ aContext->GetDrawTarget()->FlushItem(aItemBounds);
+ aGroup->PaintItemRange(this, aChildren->begin(), aChildren->end(),
+ aContext, aRecorder, aRootManager, aResources);
+ aContext->GetDrawTarget()->PopLayer();
+ GP("endGroup %s %p-%d\n", aItem->Name(), aItem->Frame(),
+ aItem->GetPerFrameKey());
+ aContext->GetDrawTarget()->FlushItem(aItemBounds);
+ break;
+ }
+ case DisplayItemType::TYPE_MASK: {
+ GP("Paint Mask\n");
+ auto maskItem = static_cast<nsDisplayMasksAndClipPaths*>(aItem);
+ if (maskItem->IsValidMask()) {
+ maskItem->PaintWithContentsPaintCallback(
+ mDisplayListBuilder, aContext, [&] {
+ GP("beginGroup %s %p-%d\n", aItem->Name(), aItem->Frame(),
+ aItem->GetPerFrameKey());
+ aContext->GetDrawTarget()->FlushItem(aItemBounds);
+ aGroup->PaintItemRange(this, aChildren->begin(), aChildren->end(),
+ aContext, aRecorder, aRootManager,
+ aResources);
+ GP("endGroup %s %p-%d\n", aItem->Name(), aItem->Frame(),
+ aItem->GetPerFrameKey());
+ });
+ TakeExternalSurfaces(aRecorder, aData->mExternalSurfaces, aRootManager,
+ aResources);
+ aContext->GetDrawTarget()->FlushItem(aItemBounds);
+ }
+ break;
+ }
+ case DisplayItemType::TYPE_FILTER: {
+ GP("Paint Filter\n");
+ // Painting will cause us to include the item's recording in the blob. We
+ // only want to do that if it is dirty, because otherwise the recording
+ // might change (e.g. due to factor of 2 scaling of images giving
+ // different results) and the merging will discard it because it is
+ // outside the invalid rect.
+ if (aDirty) {
+ auto filterItem = static_cast<nsDisplayFilters*>(aItem);
+ filterItem->Paint(mDisplayListBuilder, aContext);
+ TakeExternalSurfaces(aRecorder, aData->mExternalSurfaces, aRootManager,
+ aResources);
+ }
+ aContext->GetDrawTarget()->FlushItem(aItemBounds);
+ break;
+ }
+
+ default:
+ aGroup->PaintItemRange(this, aChildren->begin(), aChildren->end(),
+ aContext, aRecorder, aRootManager, aResources);
+ break;
+ }
+}
+
+class WebRenderGroupData : public WebRenderUserData {
+ public:
+ WebRenderGroupData(RenderRootStateManager* aWRManager, nsDisplayItem* aItem);
+ virtual ~WebRenderGroupData();
+
+ WebRenderGroupData* AsGroupData() override { return this; }
+ UserDataType GetType() override { return UserDataType::eGroup; }
+ static UserDataType Type() { return UserDataType::eGroup; }
+
+ DIGroup mSubGroup;
+ DIGroup mFollowingGroup;
+};
+
+enum class ItemActivity : uint8_t {
+ /// Item must not be active.
+ No = 0,
+ /// Could be active if it has no layerization cost.
+ /// Typically active if first of an item group.
+ Could = 1,
+ /// Should be active unless something external makes that less useful.
+ /// For example if the item is affected by a complex mask, it remains
+ /// inactive.
+ Should = 2,
+ /// Must be active regardless of external factors.
+ Must = 3,
+};
+
+ItemActivity CombineActivity(ItemActivity a, ItemActivity b) {
+ return a > b ? a : b;
+}
+
+bool ActivityAtLeast(ItemActivity rhs, ItemActivity atLeast) {
+ return rhs >= atLeast;
+}
+
+static ItemActivity IsItemProbablyActive(
+ nsDisplayItem* aItem, mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const mozilla::layers::StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder, bool aSiblingActive,
+ bool aUniformlyScaled);
+
+static ItemActivity HasActiveChildren(
+ const nsDisplayList& aList, mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const mozilla::layers::StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder, bool aUniformlyScaled) {
+ ItemActivity activity = ItemActivity::No;
+ for (nsDisplayItem* item : aList) {
+ // Here we only want to know if a child must be active, so we don't specify
+ // when the item is first or last, which can cause an item that could be
+ // either decide to be active. This is a bit conservative and avoids some
+ // extra layers. It's a good tradeoff until we get to the point where most
+ // items could have been active but none *had* to. Right now this is
+ // unlikely but as more svg items get webrenderized it will be better to
+ // make them active more aggressively.
+ auto childActivity =
+ IsItemProbablyActive(item, aBuilder, aResources, aSc, aManager,
+ aDisplayListBuilder, false, aUniformlyScaled);
+ activity = CombineActivity(activity, childActivity);
+ if (activity == ItemActivity::Must) {
+ return activity;
+ }
+ }
+ return activity;
+}
+
+static ItemActivity AssessBounds(const StackingContextHelper& aSc,
+ nsDisplayListBuilder* aDisplayListBuilder,
+ nsDisplayItem* aItem,
+ bool aHasActivePrecedingSibling) {
+ // Arbitrary threshold up for adjustments. What we want to avoid here
+ // is alternating between active and non active items and create a lot
+ // of overlapping blobs, so we only make images active if they are
+ // costly enough that it's worth the risk of having more layers. As we
+ // move more blob items into wr display items it will become less of a
+ // concern.
+ constexpr float largeish = 512;
+
+ bool snap = false;
+ nsRect bounds = aItem->GetBounds(aDisplayListBuilder, &snap);
+
+ float appUnitsPerDevPixel =
+ static_cast<float>(aItem->Frame()->PresContext()->AppUnitsPerDevPixel());
+
+ float width =
+ static_cast<float>(bounds.width) * aSc.GetInheritedScale().xScale;
+ float height =
+ static_cast<float>(bounds.height) * aSc.GetInheritedScale().yScale;
+
+ // Webrender doesn't handle primitives smaller than a pixel well, so
+ // avoid making them active.
+ if (width >= appUnitsPerDevPixel && height >= appUnitsPerDevPixel) {
+ if (aHasActivePrecedingSibling || width > largeish || height > largeish) {
+ return ItemActivity::Should;
+ }
+
+ return ItemActivity::Could;
+ }
+
+ return ItemActivity::No;
+}
+
+// This function decides whether we want to treat this item as "active", which
+// means that it's a container item which we will turn into a WebRender
+// StackingContext, or whether we treat it as "inactive" and include it inside
+// the parent blob image.
+//
+// We can't easily use GetLayerState because it wants a bunch of layers related
+// information.
+static ItemActivity IsItemProbablyActive(
+ nsDisplayItem* aItem, mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const mozilla::layers::StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder, bool aHasActivePrecedingSibling,
+ bool aUniformlyScaled) {
+ switch (aItem->GetType()) {
+ case DisplayItemType::TYPE_TRANSFORM: {
+ nsDisplayTransform* transformItem =
+ static_cast<nsDisplayTransform*>(aItem);
+ const Matrix4x4Flagged& t = transformItem->GetTransform();
+ Matrix t2d;
+ bool is2D = t.Is2D(&t2d);
+ if (!is2D) {
+ return ItemActivity::Must;
+ }
+
+ auto activity = HasActiveChildren(*transformItem->GetChildren(), aBuilder,
+ aResources, aSc, aManager,
+ aDisplayListBuilder, aUniformlyScaled);
+
+ if (transformItem->MayBeAnimated(aDisplayListBuilder)) {
+ activity = CombineActivity(activity, ItemActivity::Should);
+ }
+
+ return activity;
+ }
+ case DisplayItemType::TYPE_OPACITY: {
+ nsDisplayOpacity* opacityItem = static_cast<nsDisplayOpacity*>(aItem);
+ if (opacityItem->NeedsActiveLayer(aDisplayListBuilder,
+ opacityItem->Frame())) {
+ return ItemActivity::Must;
+ }
+ return HasActiveChildren(*opacityItem->GetChildren(), aBuilder,
+ aResources, aSc, aManager, aDisplayListBuilder,
+ aUniformlyScaled);
+ }
+ case DisplayItemType::TYPE_FOREIGN_OBJECT: {
+ return ItemActivity::Must;
+ }
+ case DisplayItemType::TYPE_SVG_GEOMETRY: {
+ auto* svgItem = static_cast<DisplaySVGGeometry*>(aItem);
+ if (StaticPrefs::gfx_webrender_svg_shapes() && aUniformlyScaled &&
+ svgItem->ShouldBeActive(aBuilder, aResources, aSc, aManager,
+ aDisplayListBuilder)) {
+ return AssessBounds(aSc, aDisplayListBuilder, aItem,
+ aHasActivePrecedingSibling);
+ }
+
+ return ItemActivity::No;
+ }
+ case DisplayItemType::TYPE_SVG_IMAGE: {
+ auto* svgItem = static_cast<DisplaySVGImage*>(aItem);
+ if (StaticPrefs::gfx_webrender_svg_images() && aUniformlyScaled &&
+ svgItem->ShouldBeActive(aBuilder, aResources, aSc, aManager,
+ aDisplayListBuilder)) {
+ return AssessBounds(aSc, aDisplayListBuilder, aItem,
+ aHasActivePrecedingSibling);
+ }
+
+ return ItemActivity::No;
+ }
+ case DisplayItemType::TYPE_BLEND_MODE: {
+ /* BLEND_MODE needs to be active if it might have a previous sibling
+ * that is active so that it's able to blend with that content. */
+ if (aHasActivePrecedingSibling) {
+ return ItemActivity::Must;
+ }
+
+ return HasActiveChildren(*aItem->GetChildren(), aBuilder, aResources, aSc,
+ aManager, aDisplayListBuilder, aUniformlyScaled);
+ }
+ case DisplayItemType::TYPE_MASK: {
+ if (aItem->GetChildren()) {
+ auto activity =
+ HasActiveChildren(*aItem->GetChildren(), aBuilder, aResources, aSc,
+ aManager, aDisplayListBuilder, aUniformlyScaled);
+ // For masked items, don't bother with making children active since we
+ // are going to have to need to paint and upload a large mask anyway.
+ if (activity < ItemActivity::Must) {
+ return ItemActivity::No;
+ }
+ return activity;
+ }
+ return ItemActivity::No;
+ }
+ case DisplayItemType::TYPE_WRAP_LIST:
+ case DisplayItemType::TYPE_CONTAINER:
+ case DisplayItemType::TYPE_PERSPECTIVE: {
+ if (aItem->GetChildren()) {
+ return HasActiveChildren(*aItem->GetChildren(), aBuilder, aResources,
+ aSc, aManager, aDisplayListBuilder,
+ aUniformlyScaled);
+ }
+ return ItemActivity::No;
+ }
+ case DisplayItemType::TYPE_FILTER: {
+ nsDisplayFilters* filters = static_cast<nsDisplayFilters*>(aItem);
+ if (filters->CanCreateWebRenderCommands()) {
+ // Items are usually expensive enough on the CPU that we want to
+ // make them active whenever we can.
+ return ItemActivity::Must;
+ }
+ return ItemActivity::No;
+ }
+ default:
+ // TODO: handle other items?
+ return ItemActivity::No;
+ }
+}
+
+// This does a pass over the display lists and will join the display items
+// into groups as well as paint them
+void Grouper::ConstructGroups(nsDisplayListBuilder* aDisplayListBuilder,
+ WebRenderCommandBuilder* aCommandBuilder,
+ wr::DisplayListBuilder& aBuilder,
+ wr::IpcResourceUpdateQueue& aResources,
+ DIGroup* aGroup, nsDisplayList* aList,
+ nsDisplayItem* aWrappingItem,
+ const StackingContextHelper& aSc) {
+ RenderRootStateManager* manager =
+ aCommandBuilder->mManager->GetRenderRootStateManager();
+
+ nsDisplayList::iterator startOfCurrentGroup = aList->end();
+ DIGroup* currentGroup = aGroup;
+
+ // We need to track whether we have active siblings for mixed blend mode.
+ bool encounteredActiveItem = false;
+ bool isFirstGroup = true;
+ // Track whether the item is the first (visible) of its group in which case
+ // making it active won't add extra layers.
+ bool isFirst = true;
+
+ for (auto it = aList->begin(); it != aList->end(); ++it) {
+ nsDisplayItem* item = *it;
+ MOZ_ASSERT(item);
+
+ if (item->HasHitTestInfo()) {
+ // Accumulate the hit-test info flags. In cases where there are multiple
+ // hittest-info display items with different flags, mHitInfo will have
+ // the union of all those flags. If that is the case, we will
+ // additionally set eIrregularArea (at the site that we use mHitInfo)
+ // so that downstream consumers of this (primarily APZ) will know that
+ // the exact shape of what gets hit with what is unknown.
+ currentGroup->mHitInfo += item->GetHitTestInfo().Info();
+ }
+
+ if (startOfCurrentGroup == aList->end()) {
+ startOfCurrentGroup = it;
+ if (!isFirstGroup) {
+ mClipManager.SwitchItem(aDisplayListBuilder, aWrappingItem);
+ }
+ }
+
+ bool isLast = it.HasNext();
+
+ // WebRender's anti-aliasing approximation is not very good under
+ // non-uniform scales.
+ bool uniformlyScaled =
+ fabs(aGroup->mScale.xScale - aGroup->mScale.yScale) < 0.1;
+
+ auto activity = IsItemProbablyActive(
+ item, aBuilder, aResources, aSc, manager, mDisplayListBuilder,
+ encounteredActiveItem, uniformlyScaled);
+ auto threshold =
+ isFirst || isLast ? ItemActivity::Could : ItemActivity::Should;
+
+ if (activity >= threshold) {
+ encounteredActiveItem = true;
+ // We're going to be starting a new group.
+ RefPtr<WebRenderGroupData> groupData =
+ aCommandBuilder->CreateOrRecycleWebRenderUserData<WebRenderGroupData>(
+ item);
+
+ groupData->mFollowingGroup.mInvalidRect.SetEmpty();
+
+ // Initialize groupData->mFollowingGroup with data from currentGroup.
+ // We want to copy out this information before calling EndGroup because
+ // EndGroup will set mLastVisibleRect depending on whether
+ // we send something to WebRender.
+
+ // TODO: compute the group bounds post-grouping, so that they can be
+ // tighter for just the sublist that made it into this group.
+ // We want to ensure the tight bounds are still clipped by area
+ // that we're building the display list for.
+ if (groupData->mFollowingGroup.mScale != currentGroup->mScale ||
+ groupData->mFollowingGroup.mAppUnitsPerDevPixel !=
+ currentGroup->mAppUnitsPerDevPixel ||
+ groupData->mFollowingGroup.mResidualOffset !=
+ currentGroup->mResidualOffset) {
+ if (groupData->mFollowingGroup.mAppUnitsPerDevPixel !=
+ currentGroup->mAppUnitsPerDevPixel) {
+ GP("app unit change following: %d %d\n",
+ groupData->mFollowingGroup.mAppUnitsPerDevPixel,
+ currentGroup->mAppUnitsPerDevPixel);
+ }
+ // The group changed size
+ GP("Inner group size change\n");
+ groupData->mFollowingGroup.ClearItems();
+ groupData->mFollowingGroup.ClearImageKey(
+ aCommandBuilder->mManager->GetRenderRootStateManager());
+ }
+ groupData->mFollowingGroup.mAppUnitsPerDevPixel =
+ currentGroup->mAppUnitsPerDevPixel;
+ groupData->mFollowingGroup.mLayerBounds = currentGroup->mLayerBounds;
+ groupData->mFollowingGroup.mClippedImageBounds =
+ currentGroup->mClippedImageBounds;
+ groupData->mFollowingGroup.mScale = currentGroup->mScale;
+ groupData->mFollowingGroup.mResidualOffset =
+ currentGroup->mResidualOffset;
+ groupData->mFollowingGroup.mVisibleRect = currentGroup->mVisibleRect;
+ groupData->mFollowingGroup.mPreservedRect =
+ groupData->mFollowingGroup.mVisibleRect.Intersect(
+ groupData->mFollowingGroup.mLastVisibleRect);
+ groupData->mFollowingGroup.mActualBounds = LayerIntRect();
+ groupData->mFollowingGroup.mHitTestBounds = LayerIntRect();
+ groupData->mFollowingGroup.mHitInfo = currentGroup->mHitInfo;
+
+ currentGroup->EndGroup(aCommandBuilder->mManager, aDisplayListBuilder,
+ aBuilder, aResources, this, startOfCurrentGroup,
+ it);
+
+ {
+ auto spaceAndClipChain =
+ mClipManager.SwitchItem(aDisplayListBuilder, item);
+ wr::SpaceAndClipChainHelper saccHelper(aBuilder, spaceAndClipChain);
+ bool hasHitTest = mHitTestInfoManager.ProcessItem(item, aBuilder,
+ aDisplayListBuilder);
+ // XXX - This is hacky. Some items have hit testing info on them but we
+ // also have dedicated hit testing items, the flags of which apply to
+ // the the group that contains them. We don't want layerization to
+ // affect that so if the item didn't emit any hit testing then we still
+ // push a hit test item if the previous group had some hit test flags
+ // set. This is obviously not great. Hit testing should be independent
+ // from how we layerize.
+ if (!hasHitTest &&
+ currentGroup->mHitInfo != gfx::CompositorHitTestInvisibleToHit) {
+ auto hitTestRect = item->GetBuildingRect();
+ if (!hitTestRect.IsEmpty()) {
+ currentGroup->PushHitTest(
+ aBuilder, LayoutDeviceRect::FromAppUnits(
+ hitTestRect, currentGroup->mAppUnitsPerDevPixel));
+ }
+ }
+
+ sIndent++;
+ // Note: this call to CreateWebRenderCommands can recurse back into
+ // this function.
+ bool createdWRCommands = item->CreateWebRenderCommands(
+ aBuilder, aResources, aSc, manager, mDisplayListBuilder);
+ MOZ_RELEASE_ASSERT(
+ createdWRCommands,
+ "active transforms should always succeed at creating "
+ "WebRender commands");
+ sIndent--;
+ }
+
+ isFirstGroup = false;
+ startOfCurrentGroup = aList->end();
+ currentGroup = &groupData->mFollowingGroup;
+ isFirst = true;
+ } else { // inactive item
+ bool isInvisible = false;
+ ConstructItemInsideInactive(aCommandBuilder, aBuilder, aResources,
+ currentGroup, item, aSc, &isInvisible);
+ if (!isInvisible) {
+ // Invisible items don't count.
+ isFirst = false;
+ }
+ }
+ }
+
+ currentGroup->EndGroup(aCommandBuilder->mManager, aDisplayListBuilder,
+ aBuilder, aResources, this, startOfCurrentGroup,
+ aList->end());
+}
+
+// This does a pass over the display lists and will join the display items
+// into a single group.
+bool Grouper::ConstructGroupInsideInactive(
+ WebRenderCommandBuilder* aCommandBuilder, wr::DisplayListBuilder& aBuilder,
+ wr::IpcResourceUpdateQueue& aResources, DIGroup* aGroup,
+ nsDisplayList* aList, const StackingContextHelper& aSc) {
+ bool invalidated = false;
+ for (nsDisplayItem* item : *aList) {
+ if (item->HasHitTestInfo()) {
+ // Accumulate the hit-test info flags. In cases where there are multiple
+ // hittest-info display items with different flags, mHitInfo will have
+ // the union of all those flags. If that is the case, we will
+ // additionally set eIrregularArea (at the site that we use mHitInfo)
+ // so that downstream consumers of this (primarily APZ) will know that
+ // the exact shape of what gets hit with what is unknown.
+ aGroup->mHitInfo += item->GetHitTestInfo().Info();
+ }
+
+ bool invisible = false;
+ invalidated |= ConstructItemInsideInactive(
+ aCommandBuilder, aBuilder, aResources, aGroup, item, aSc, &invisible);
+ }
+ return invalidated;
+}
+
+bool Grouper::ConstructItemInsideInactive(
+ WebRenderCommandBuilder* aCommandBuilder, wr::DisplayListBuilder& aBuilder,
+ wr::IpcResourceUpdateQueue& aResources, DIGroup* aGroup,
+ nsDisplayItem* aItem, const StackingContextHelper& aSc,
+ bool* aOutIsInvisible) {
+ nsDisplayList* children = aItem->GetChildren();
+ BlobItemData* data = GetBlobItemDataForGroup(aItem, aGroup);
+
+ /* mInvalid unfortunately persists across paints. Clear it so that if we don't
+ * set it to 'true' we ensure that we're not using the value from the last
+ * time that we painted */
+ data->mInvalid = false;
+ data->mInvisible = aItem->IsInvisible();
+ *aOutIsInvisible = data->mInvisible;
+
+ // we compute the geometry change here because we have the transform around
+ // still
+ bool invalidated = aGroup->ComputeGeometryChange(aItem, data, mTransform,
+ mDisplayListBuilder);
+
+ // Temporarily restrict the image bounds to the bounds of the container so
+ // that clipped children within the container know about the clip. This
+ // ensures that the bounds passed to FlushItem are contained in the bounds of
+ // the clip so that we don't include items in the recording without including
+ // their corresponding clipping items.
+ auto oldClippedImageBounds = aGroup->mClippedImageBounds;
+ aGroup->mClippedImageBounds =
+ aGroup->mClippedImageBounds.Intersect(data->mRect);
+
+ if (aItem->GetType() == DisplayItemType::TYPE_FILTER) {
+ // If ConstructGroupInsideInactive finds any change, we invalidate the
+ // entire container item. This is needed because blob merging requires the
+ // entire item to be within the invalid region.
+ Matrix m = mTransform;
+ mTransform = Matrix();
+ sIndent++;
+ if (ConstructGroupInsideInactive(aCommandBuilder, aBuilder, aResources,
+ aGroup, children, aSc)) {
+ data->mInvalid = true;
+ aGroup->InvalidateRect(data->mRect);
+ invalidated = true;
+ }
+ sIndent--;
+ mTransform = m;
+ } else if (aItem->GetType() == DisplayItemType::TYPE_TRANSFORM) {
+ Matrix m = mTransform;
+ nsDisplayTransform* transformItem = static_cast<nsDisplayTransform*>(aItem);
+ const Matrix4x4Flagged& t = transformItem->GetTransform();
+ Matrix t2d;
+ bool is2D = t.CanDraw2D(&t2d);
+ if (!is2D) {
+ // If ConstructGroupInsideInactive finds any change, we invalidate the
+ // entire container item. This is needed because blob merging requires the
+ // entire item to be within the invalid region.
+ mTransform = Matrix();
+ sIndent++;
+ if (ConstructGroupInsideInactive(aCommandBuilder, aBuilder, aResources,
+ aGroup, children, aSc)) {
+ data->mInvalid = true;
+ aGroup->InvalidateRect(data->mRect);
+ invalidated = true;
+ }
+ sIndent--;
+ } else {
+ GP("t2d: %f %f\n", t2d._31, t2d._32);
+ mTransform.PreMultiply(t2d);
+ GP("mTransform: %f %f\n", mTransform._31, mTransform._32);
+ sIndent++;
+ invalidated |= ConstructGroupInsideInactive(
+ aCommandBuilder, aBuilder, aResources, aGroup, children, aSc);
+ sIndent--;
+ }
+ mTransform = m;
+ } else if (children) {
+ sIndent++;
+ invalidated |= ConstructGroupInsideInactive(
+ aCommandBuilder, aBuilder, aResources, aGroup, children, aSc);
+ sIndent--;
+ }
+
+ GP("Including %s of %d\n", aItem->Name(), aGroup->mDisplayItems.Count());
+ aGroup->mClippedImageBounds = oldClippedImageBounds;
+ return invalidated;
+}
+
+/* This is just a copy of nsRect::ScaleToOutsidePixels with an offset added in.
+ * The offset is applied just before the rounding. It's in the scaled space. */
+static mozilla::LayerIntRect ScaleToOutsidePixelsOffset(
+ nsRect aRect, float aXScale, float aYScale, nscoord aAppUnitsPerPixel,
+ LayerPoint aOffset) {
+ mozilla::LayerIntRect rect;
+ rect.SetNonEmptyBox(
+ NSToIntFloor(NSAppUnitsToFloatPixels(aRect.x, float(aAppUnitsPerPixel)) *
+ aXScale +
+ aOffset.x),
+ NSToIntFloor(NSAppUnitsToFloatPixels(aRect.y, float(aAppUnitsPerPixel)) *
+ aYScale +
+ aOffset.y),
+ NSToIntCeil(
+ NSAppUnitsToFloatPixels(aRect.XMost(), float(aAppUnitsPerPixel)) *
+ aXScale +
+ aOffset.x),
+ NSToIntCeil(
+ NSAppUnitsToFloatPixels(aRect.YMost(), float(aAppUnitsPerPixel)) *
+ aYScale +
+ aOffset.y));
+ return rect;
+}
+
+/* This function is the same as the above except that it rounds to the
+ * nearest instead of rounding out. We use it for attempting to compute the
+ * actual pixel bounds of opaque items */
+static mozilla::gfx::IntRect ScaleToNearestPixelsOffset(
+ nsRect aRect, float aXScale, float aYScale, nscoord aAppUnitsPerPixel,
+ LayerPoint aOffset) {
+ mozilla::gfx::IntRect rect;
+ rect.SetNonEmptyBox(
+ NSToIntFloor(NSAppUnitsToFloatPixels(aRect.x, float(aAppUnitsPerPixel)) *
+ aXScale +
+ aOffset.x + 0.5),
+ NSToIntFloor(NSAppUnitsToFloatPixels(aRect.y, float(aAppUnitsPerPixel)) *
+ aYScale +
+ aOffset.y + 0.5),
+ NSToIntFloor(
+ NSAppUnitsToFloatPixels(aRect.XMost(), float(aAppUnitsPerPixel)) *
+ aXScale +
+ aOffset.x + 0.5),
+ NSToIntFloor(
+ NSAppUnitsToFloatPixels(aRect.YMost(), float(aAppUnitsPerPixel)) *
+ aYScale +
+ aOffset.y + 0.5));
+ return rect;
+}
+
+RenderRootStateManager* WebRenderCommandBuilder::GetRenderRootStateManager() {
+ return mManager->GetRenderRootStateManager();
+}
+
+void WebRenderCommandBuilder::DoGroupingForDisplayList(
+ nsDisplayList* aList, nsDisplayItem* aWrappingItem,
+ nsDisplayListBuilder* aDisplayListBuilder, const StackingContextHelper& aSc,
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources) {
+ if (!aList->GetBottom()) {
+ return;
+ }
+
+ GP("DoGroupingForDisplayList\n");
+
+ mClipManager.BeginList(aSc);
+ mHitTestInfoManager.Reset();
+ Grouper g(mClipManager);
+
+ int32_t appUnitsPerDevPixel =
+ aWrappingItem->Frame()->PresContext()->AppUnitsPerDevPixel();
+
+ g.mDisplayListBuilder = aDisplayListBuilder;
+ RefPtr<WebRenderGroupData> groupData =
+ CreateOrRecycleWebRenderUserData<WebRenderGroupData>(aWrappingItem);
+
+ bool snapped;
+ nsRect groupBounds =
+ aWrappingItem->GetUntransformedBounds(aDisplayListBuilder, &snapped);
+ DIGroup& group = groupData->mSubGroup;
+
+ auto scale = aSc.GetInheritedScale();
+ GP("Inherited scale %f %f\n", scale.xScale, scale.yScale);
+
+ auto trans =
+ ViewAs<LayerPixel>(aSc.GetSnappingSurfaceTransform().GetTranslation());
+ auto snappedTrans = LayerIntPoint::Floor(trans);
+ LayerPoint residualOffset = trans - snappedTrans;
+
+ auto layerBounds =
+ ScaleToOutsidePixelsOffset(groupBounds, scale.xScale, scale.yScale,
+ appUnitsPerDevPixel, residualOffset);
+
+ const nsRect& untransformedPaintRect =
+ aWrappingItem->GetUntransformedPaintRect();
+
+ auto visibleRect = ScaleToOutsidePixelsOffset(
+ untransformedPaintRect, scale.xScale, scale.yScale,
+ appUnitsPerDevPixel, residualOffset)
+ .Intersect(layerBounds);
+
+ GP("LayerBounds: %d %d %d %d\n", layerBounds.x, layerBounds.y,
+ layerBounds.width, layerBounds.height);
+ GP("VisibleRect: %d %d %d %d\n", visibleRect.x, visibleRect.y,
+ visibleRect.width, visibleRect.height);
+
+ GP("Inherited scale %f %f\n", scale.xScale, scale.yScale);
+
+ group.mInvalidRect.SetEmpty();
+ if (group.mAppUnitsPerDevPixel != appUnitsPerDevPixel ||
+ group.mScale != scale || group.mResidualOffset != residualOffset) {
+ GP("Property change. Deleting blob\n");
+
+ if (group.mAppUnitsPerDevPixel != appUnitsPerDevPixel) {
+ GP(" App unit change %d -> %d\n", group.mAppUnitsPerDevPixel,
+ appUnitsPerDevPixel);
+ }
+
+ if (group.mScale != scale) {
+ GP(" Scale %f %f -> %f %f\n", group.mScale.xScale, group.mScale.yScale,
+ scale.xScale, scale.yScale);
+ }
+
+ if (group.mResidualOffset != residualOffset) {
+ GP(" Residual Offset %f %f -> %f %f\n", group.mResidualOffset.x.value,
+ group.mResidualOffset.y.value, residualOffset.x.value,
+ residualOffset.y.value);
+ }
+
+ group.ClearItems();
+ group.ClearImageKey(mManager->GetRenderRootStateManager());
+ }
+
+ ScrollableLayerGuid::ViewID scrollId = ScrollableLayerGuid::NULL_SCROLL_ID;
+ if (const ActiveScrolledRoot* asr = aWrappingItem->GetActiveScrolledRoot()) {
+ scrollId = asr->GetViewId();
+ }
+
+ g.mAppUnitsPerDevPixel = appUnitsPerDevPixel;
+ group.mResidualOffset = residualOffset;
+ group.mLayerBounds = layerBounds;
+ group.mVisibleRect = visibleRect;
+ group.mActualBounds = LayerIntRect();
+ group.mHitTestBounds = LayerIntRect();
+ group.mPreservedRect = group.mVisibleRect.Intersect(group.mLastVisibleRect);
+ group.mAppUnitsPerDevPixel = appUnitsPerDevPixel;
+ group.mClippedImageBounds = layerBounds;
+
+ g.mTransform =
+ Matrix::Scaling(scale).PostTranslate(residualOffset.x, residualOffset.y);
+ group.mScale = scale;
+ group.mScrollId = scrollId;
+ g.ConstructGroups(aDisplayListBuilder, this, aBuilder, aResources, &group,
+ aList, aWrappingItem, aSc);
+ mClipManager.EndList(aSc);
+}
+
+WebRenderCommandBuilder::WebRenderCommandBuilder(
+ WebRenderLayerManager* aManager)
+ : mManager(aManager),
+ mLastAsr(nullptr),
+ mBuilderDumpIndex(0),
+ mDumpIndent(0),
+ mDoGrouping(false),
+ mContainsSVGGroup(false) {}
+
+void WebRenderCommandBuilder::Destroy() {
+ mLastCanvasDatas.Clear();
+ ClearCachedResources();
+}
+
+void WebRenderCommandBuilder::EmptyTransaction() {
+ // We need to update canvases that might have changed.
+ for (RefPtr<WebRenderCanvasData> canvasData : mLastCanvasDatas) {
+ WebRenderCanvasRendererAsync* canvas = canvasData->GetCanvasRenderer();
+ if (canvas) {
+ canvas->UpdateCompositableClientForEmptyTransaction();
+ }
+ }
+}
+
+bool WebRenderCommandBuilder::NeedsEmptyTransaction() {
+ return !mLastCanvasDatas.IsEmpty();
+}
+
+void WebRenderCommandBuilder::BuildWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder,
+ wr::IpcResourceUpdateQueue& aResourceUpdates, nsDisplayList* aDisplayList,
+ nsDisplayListBuilder* aDisplayListBuilder, WebRenderScrollData& aScrollData,
+ WrFiltersHolder&& aFilters) {
+ AUTO_PROFILER_LABEL_CATEGORY_PAIR(GRAPHICS_WRDisplayList);
+
+ StackingContextHelper sc;
+ aScrollData = WebRenderScrollData(mManager, aDisplayListBuilder);
+ MOZ_ASSERT(mLayerScrollData.empty());
+ mClipManager.BeginBuild(mManager, aBuilder);
+ mHitTestInfoManager.Reset();
+
+ mBuilderDumpIndex = 0;
+ mLastCanvasDatas.Clear();
+ mLastAsr = nullptr;
+ mContainsSVGGroup = false;
+ MOZ_ASSERT(mDumpIndent == 0);
+
+ {
+ wr::StackingContextParams params;
+ params.mRootReferenceFrame = aDisplayListBuilder->RootReferenceFrame();
+ params.mFilters = std::move(aFilters.filters);
+ params.mFilterDatas = std::move(aFilters.filter_datas);
+ params.clip =
+ wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId());
+
+ StackingContextHelper pageRootSc(sc, nullptr, nullptr, nullptr, aBuilder,
+ params);
+ if (ShouldDumpDisplayList(aDisplayListBuilder)) {
+ mBuilderDumpIndex =
+ aBuilder.Dump(mDumpIndent + 1, Some(mBuilderDumpIndex), Nothing());
+ }
+ CreateWebRenderCommandsFromDisplayList(aDisplayList, nullptr,
+ aDisplayListBuilder, pageRootSc,
+ aBuilder, aResourceUpdates);
+ }
+
+ // Make a "root" layer data that has everything else as descendants
+ mLayerScrollData.emplace_back();
+ mLayerScrollData.back().InitializeRoot(mLayerScrollData.size() - 1);
+ auto callback =
+ [&aScrollData](ScrollableLayerGuid::ViewID aScrollId) -> bool {
+ return aScrollData.HasMetadataFor(aScrollId).isSome();
+ };
+ Maybe<ScrollMetadata> rootMetadata =
+ nsLayoutUtils::GetRootMetadata(aDisplayListBuilder, mManager, callback);
+ if (rootMetadata) {
+ // Put the fallback root metadata on the rootmost layer that is
+ // a matching async zoom container, or the root layer that we just
+ // created above.
+ size_t rootMetadataTarget = mLayerScrollData.size() - 1;
+ for (size_t i = rootMetadataTarget; i > 0; i--) {
+ if (auto zoomContainerId =
+ mLayerScrollData[i - 1].GetAsyncZoomContainerId()) {
+ if (*zoomContainerId == rootMetadata->GetMetrics().GetScrollId()) {
+ rootMetadataTarget = i - 1;
+ break;
+ }
+ }
+ }
+ mLayerScrollData[rootMetadataTarget].AppendScrollMetadata(
+ aScrollData, rootMetadata.ref());
+ }
+
+ // Append the WebRenderLayerScrollData items into WebRenderScrollData
+ // in reverse order, from topmost to bottommost. This is in keeping with
+ // the semantics of WebRenderScrollData.
+ for (auto it = mLayerScrollData.rbegin(); it != mLayerScrollData.rend();
+ it++) {
+ aScrollData.AddLayerData(std::move(*it));
+ }
+ mLayerScrollData.clear();
+ mClipManager.EndBuild();
+
+ // Remove the user data those are not displayed on the screen and
+ // also reset the data to unused for next transaction.
+ RemoveUnusedAndResetWebRenderUserData();
+}
+
+bool WebRenderCommandBuilder::ShouldDumpDisplayList(
+ nsDisplayListBuilder* aBuilder) {
+ return aBuilder && aBuilder->IsInActiveDocShell() &&
+ ((XRE_IsParentProcess() &&
+ StaticPrefs::gfx_webrender_debug_dl_dump_parent()) ||
+ (XRE_IsContentProcess() &&
+ StaticPrefs::gfx_webrender_debug_dl_dump_content()));
+}
+
+void WebRenderCommandBuilder::CreateWebRenderCommands(
+ nsDisplayItem* aItem, mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ mHitTestInfoManager.ProcessItem(aItem, aBuilder, aDisplayListBuilder);
+ if (aItem->GetType() == DisplayItemType::TYPE_COMPOSITOR_HITTEST_INFO) {
+ // The hit test information was processed above.
+ return;
+ }
+
+ auto* item = aItem->AsPaintedDisplayItem();
+ MOZ_RELEASE_ASSERT(item, "Tried to paint item that cannot be painted");
+
+ if (aBuilder.ReuseItem(item)) {
+ // No further processing should be needed, since the item was reused.
+ return;
+ }
+
+ RenderRootStateManager* manager = mManager->GetRenderRootStateManager();
+
+ // Note: this call to CreateWebRenderCommands can recurse back into
+ // this function if the |item| is a wrapper for a sublist.
+ const bool createdWRCommands = aItem->CreateWebRenderCommands(
+ aBuilder, aResources, aSc, manager, aDisplayListBuilder);
+
+ if (!createdWRCommands) {
+ PushItemAsImage(aItem, aBuilder, aResources, aSc, aDisplayListBuilder);
+ }
+}
+
+// A helper struct to store information needed when creating a new
+// WebRenderLayerScrollData in CreateWebRenderCommandsFromDisplayList().
+// This information is gathered before the recursion, and then used to
+// emit the new layer after the recursion.
+struct NewLayerData {
+ size_t mLayerCountBeforeRecursing = 0;
+ const ActiveScrolledRoot* mStopAtAsr = nullptr;
+
+ // Information pertaining to the deferred transform.
+ nsDisplayTransform* mDeferredItem = nullptr;
+ ScrollableLayerGuid::ViewID mDeferredId = ScrollableLayerGuid::NULL_SCROLL_ID;
+ bool mTransformShouldGetOwnLayer = false;
+
+ void ComputeDeferredTransformInfo(
+ const StackingContextHelper& aSc, nsDisplayItem* aItem,
+ nsDisplayTransform* aLastDeferredTransform) {
+ // See the comments on StackingContextHelper::mDeferredTransformItem
+ // for an overview of what deferred transforms are.
+ // In the case where we deferred a transform, but have a child display
+ // item with a different ASR than the deferred transform item, we cannot
+ // put the transform on the WebRenderLayerScrollData item for the child.
+ // We cannot do this because it will not conform to APZ's expectations
+ // with respect to how the APZ tree ends up structured. In particular,
+ // the GetTransformToThis() for the child APZ (which is created for the
+ // child item's ASR) will not include the transform when we actually do
+ // want it to.
+ // When we run into this scenario, we solve it by creating two
+ // WebRenderLayerScrollData items; one that just holds the transform,
+ // that we deferred, and a child WebRenderLayerScrollData item that
+ // holds the scroll metadata for the child's ASR.
+ mDeferredItem = aSc.GetDeferredTransformItem();
+ // If this deferred transform is already slated to be emitted onto an
+ // ancestor layer, do not emit it on this layer as well. Note that it's
+ // sufficient to check the most recently deferred item here, because
+ // there's only one per stacking context, and we emit it when changing
+ // stacking contexts.
+ if (mDeferredItem == aLastDeferredTransform) {
+ mDeferredItem = nullptr;
+ }
+ if (mDeferredItem) {
+ // It's possible the transform's ASR is not only an ancestor of
+ // the item's ASR, but an ancestor of stopAtAsr. In such cases,
+ // don't use the transform at all at this level (it would be
+ // scrolled by stopAtAsr which is incorrect). The transform will
+ // instead be emitted as part of the ancestor WebRenderLayerScrollData
+ // node (the one with stopAtAsr as its item ASR), or one of its
+ // ancetors in turn.
+ if (ActiveScrolledRoot::IsProperAncestor(
+ mDeferredItem->GetActiveScrolledRoot(), mStopAtAsr)) {
+ mDeferredItem = nullptr;
+ }
+ }
+ if (mDeferredItem) {
+ if (const auto* asr = mDeferredItem->GetActiveScrolledRoot()) {
+ mDeferredId = asr->GetViewId();
+ }
+ if (mDeferredItem->GetActiveScrolledRoot() !=
+ aItem->GetActiveScrolledRoot()) {
+ mTransformShouldGetOwnLayer = true;
+ } else if (aItem->GetType() == DisplayItemType::TYPE_SCROLL_INFO_LAYER) {
+ // A scroll info layer has its own scroll id that's not reflected
+ // in item->GetActiveScrolledRoot(), but will be added to the
+ // WebRenderLayerScrollData node, so it needs to be treated as
+ // having a distinct ASR from the deferred transform item.
+ mTransformShouldGetOwnLayer = true;
+ }
+ }
+ }
+};
+
+void WebRenderCommandBuilder::CreateWebRenderCommandsFromDisplayList(
+ nsDisplayList* aDisplayList, nsDisplayItem* aWrappingItem,
+ nsDisplayListBuilder* aDisplayListBuilder, const StackingContextHelper& aSc,
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ bool aNewClipList) {
+ if (mDoGrouping) {
+ MOZ_RELEASE_ASSERT(
+ aWrappingItem,
+ "Only the root list should have a null wrapping item, and mDoGrouping "
+ "should never be true for the root list.");
+ GP("actually entering the grouping code\n");
+ DoGroupingForDisplayList(aDisplayList, aWrappingItem, aDisplayListBuilder,
+ aSc, aBuilder, aResources);
+ return;
+ }
+
+ bool dumpEnabled = ShouldDumpDisplayList(aDisplayListBuilder);
+ if (dumpEnabled) {
+ // If we're inside a nested display list, print the WR DL items from the
+ // wrapper item before we start processing the nested items.
+ mBuilderDumpIndex =
+ aBuilder.Dump(mDumpIndent + 1, Some(mBuilderDumpIndex), Nothing());
+ }
+
+ FlattenedDisplayListIterator iter(aDisplayListBuilder, aDisplayList);
+ if (!iter.HasNext()) {
+ return;
+ }
+
+ mDumpIndent++;
+ if (aNewClipList) {
+ mClipManager.BeginList(aSc);
+ }
+
+ const bool apzEnabled = mManager->AsyncPanZoomEnabled();
+ do {
+ nsDisplayItem* item = iter.GetNextItem();
+
+ DisplayItemType itemType = item->GetType();
+
+ // If this is a new (not retained/reused) item, then we need to disable
+ // the display item cache for descendants, since it's possible that some of
+ // them got cached with a flattened opacity values., which may no longer be
+ // applied.
+ Maybe<AutoDisplayItemCacheSuppressor> cacheSuppressor;
+
+ if (itemType == DisplayItemType::TYPE_OPACITY) {
+ nsDisplayOpacity* opacity = static_cast<nsDisplayOpacity*>(item);
+
+ if (!opacity->IsReused()) {
+ cacheSuppressor.emplace(aBuilder.GetDisplayItemCache());
+ }
+
+ if (opacity->CanApplyOpacityToChildren(
+ mManager->GetRenderRootStateManager()->LayerManager(),
+ aDisplayListBuilder, aBuilder.GetInheritedOpacity())) {
+ // If all our children support handling the opacity directly, then push
+ // the opacity and clip onto the builder and skip creating a stacking
+ // context.
+ float oldOpacity = aBuilder.GetInheritedOpacity();
+ const DisplayItemClipChain* oldClip = aBuilder.GetInheritedClipChain();
+ aBuilder.SetInheritedOpacity(oldOpacity * opacity->GetOpacity());
+ aBuilder.PushInheritedClipChain(aDisplayListBuilder,
+ opacity->GetClipChain());
+
+ CreateWebRenderCommandsFromDisplayList(opacity->GetChildren(), item,
+ aDisplayListBuilder, aSc,
+ aBuilder, aResources, false);
+
+ aBuilder.SetInheritedOpacity(oldOpacity);
+ aBuilder.SetInheritedClipChain(oldClip);
+ continue;
+ }
+ }
+
+ // If this is an unscrolled background color item, in the root display list
+ // for the parent process, consider doing opaque checks.
+ if (XRE_IsParentProcess() && !aWrappingItem &&
+ itemType == DisplayItemType::TYPE_BACKGROUND_COLOR &&
+ !item->GetActiveScrolledRoot() &&
+ item->GetClip().GetRoundedRectCount() == 0) {
+ bool snap;
+ nsRegion opaque = item->GetOpaqueRegion(aDisplayListBuilder, &snap);
+ if (opaque.GetNumRects() == 1) {
+ nsRect clippedOpaque =
+ item->GetClip().ApplyNonRoundedIntersection(opaque.GetBounds());
+ if (!clippedOpaque.IsEmpty()) {
+ aDisplayListBuilder->AddWindowOpaqueRegion(item->Frame(),
+ clippedOpaque);
+ }
+ }
+ }
+
+ Maybe<NewLayerData> newLayerData;
+ if (apzEnabled) {
+ // For some types of display items we want to force a new
+ // WebRenderLayerScrollData object, to ensure we preserve the APZ-relevant
+ // data that is in the display item.
+ if (item->UpdateScrollData(nullptr, nullptr)) {
+ newLayerData = Some(NewLayerData());
+ }
+
+ // Anytime the ASR changes we also want to force a new layer data because
+ // the stack of scroll metadata is going to be different for this
+ // display item than previously, so we can't squash the display items
+ // into the same "layer".
+ const ActiveScrolledRoot* asr = item->GetActiveScrolledRoot();
+ if (asr != mLastAsr) {
+ mLastAsr = asr;
+ newLayerData = Some(NewLayerData());
+ }
+
+ // Refer to the comment on StackingContextHelper::mDeferredTransformItem
+ // for an overview of what this is about. This bit of code applies to the
+ // case where we are deferring a transform item, and we then need to defer
+ // another transform with a different ASR. In such a case we cannot just
+ // merge the deferred transforms, but need to force a new
+ // WebRenderLayerScrollData item to flush the old deferred transform, so
+ // that we can then start deferring the new one.
+ if (!newLayerData && item->CreatesStackingContextHelper() &&
+ aSc.GetDeferredTransformItem() &&
+ aSc.GetDeferredTransformItem()->GetActiveScrolledRoot() != asr) {
+ newLayerData = Some(NewLayerData());
+ }
+
+ // If we're going to create a new layer data for this item, stash the
+ // ASR so that if we recurse into a sublist they will know where to stop
+ // walking up their ASR chain when building scroll metadata.
+ if (newLayerData) {
+ newLayerData->mLayerCountBeforeRecursing = mLayerScrollData.size();
+ newLayerData->mStopAtAsr =
+ mAsrStack.empty() ? nullptr : mAsrStack.back();
+ newLayerData->ComputeDeferredTransformInfo(
+ aSc, item,
+ mDeferredTransformStack.empty() ? nullptr
+ : mDeferredTransformStack.back());
+
+ // Ensure our children's |stopAtAsr| is not be an ancestor of our
+ // |stopAtAsr|, otherwise we could get cyclic scroll metadata
+ // annotations.
+ const ActiveScrolledRoot* stopAtAsrForChildren =
+ ActiveScrolledRoot::PickDescendant(asr, newLayerData->mStopAtAsr);
+ // Additionally, while unusual and probably indicative of a poorly
+ // behaved display list, it's possible to have a deferred transform item
+ // which we will emit as its own layer on the way out of the recursion,
+ // whose ASR (let's call it T) is a *descendant* of the current item's
+ // ASR. In such cases, make sure our children have stopAtAsr=T,
+ // otherwise ASRs in the range [T, asr) may be emitted in duplicate,
+ // leading again to cylic scroll metadata annotations.
+ if (newLayerData->mTransformShouldGetOwnLayer) {
+ stopAtAsrForChildren = ActiveScrolledRoot::PickDescendant(
+ stopAtAsrForChildren,
+ newLayerData->mDeferredItem->GetActiveScrolledRoot());
+ }
+ mAsrStack.push_back(stopAtAsrForChildren);
+
+ // If we're going to emit a deferred transform onto this layer,
+ // keep track of that so descendant layers know not to emit the
+ // same deferred transform.
+ if (newLayerData->mDeferredItem) {
+ mDeferredTransformStack.push_back(newLayerData->mDeferredItem);
+ }
+ }
+ }
+
+ // This is where we emulate the clip/scroll stack that was previously
+ // implemented on the WR display list side.
+ auto spaceAndClipChain = mClipManager.SwitchItem(aDisplayListBuilder, item);
+ wr::SpaceAndClipChainHelper saccHelper(aBuilder, spaceAndClipChain);
+
+ { // scope restoreDoGrouping
+ AutoRestore<bool> restoreDoGrouping(mDoGrouping);
+ if (itemType == DisplayItemType::TYPE_SVG_WRAPPER) {
+ // Inside an <svg>, all display items that are not LAYER_ACTIVE wrapper
+ // display items (like animated transforms / opacity) share the same
+ // animated geometry root, so we can combine subsequent items of that
+ // type into the same image.
+ mContainsSVGGroup = mDoGrouping = true;
+ GP("attempting to enter the grouping code\n");
+ }
+
+ if (dumpEnabled) {
+ std::stringstream ss;
+ nsIFrame::PrintDisplayItem(aDisplayListBuilder, item, ss,
+ static_cast<uint32_t>(mDumpIndent));
+ printf_stderr("%s", ss.str().c_str());
+ }
+
+ CreateWebRenderCommands(item, aBuilder, aResources, aSc,
+ aDisplayListBuilder);
+
+ if (dumpEnabled) {
+ mBuilderDumpIndex =
+ aBuilder.Dump(mDumpIndent + 1, Some(mBuilderDumpIndex), Nothing());
+ }
+ }
+
+ if (apzEnabled) {
+ if (newLayerData) {
+ // Pop the thing we pushed before the recursion, so the topmost item on
+ // the stack is enclosing display item's ASR (or the stack is empty)
+ mAsrStack.pop_back();
+
+ if (newLayerData->mDeferredItem) {
+ MOZ_ASSERT(!mDeferredTransformStack.empty());
+ mDeferredTransformStack.pop_back();
+ }
+
+ const ActiveScrolledRoot* stopAtAsr = newLayerData->mStopAtAsr;
+
+ int32_t descendants =
+ mLayerScrollData.size() - newLayerData->mLayerCountBeforeRecursing;
+
+ nsDisplayTransform* deferred = newLayerData->mDeferredItem;
+ ScrollableLayerGuid::ViewID deferredId = newLayerData->mDeferredId;
+
+ if (newLayerData->mTransformShouldGetOwnLayer) {
+ // This creates the child WebRenderLayerScrollData for |item|, but
+ // omits the transform (hence the Nothing() as the last argument to
+ // Initialize(...)). We also need to make sure that the ASR from
+ // the deferred transform item is not on this node, so we use that
+ // ASR as the "stop at" ASR for this WebRenderLayerScrollData.
+ mLayerScrollData.emplace_back();
+ mLayerScrollData.back().Initialize(
+ mManager->GetScrollData(), item, descendants,
+ deferred->GetActiveScrolledRoot(), Nothing(),
+ ScrollableLayerGuid::NULL_SCROLL_ID);
+
+ // The above WebRenderLayerScrollData will also be a descendant of
+ // the transform-holding WebRenderLayerScrollData we create below.
+ descendants++;
+
+ // This creates the WebRenderLayerScrollData for the deferred
+ // transform item. This holds the transform matrix and the remaining
+ // ASRs needed to complete the ASR chain (i.e. the ones from the
+ // stopAtAsr down to the deferred transform item's ASR, which must be
+ // "between" stopAtAsr and |item|'s ASR in the ASR tree).
+ mLayerScrollData.emplace_back();
+ mLayerScrollData.back().Initialize(
+ mManager->GetScrollData(), deferred, descendants, stopAtAsr,
+ aSc.GetDeferredTransformMatrix(), deferredId);
+ } else {
+ // This is the "simple" case where we don't need to create two
+ // WebRenderLayerScrollData items; we can just create one that also
+ // holds the deferred transform matrix, if any.
+ mLayerScrollData.emplace_back();
+ mLayerScrollData.back().Initialize(
+ mManager->GetScrollData(), item, descendants, stopAtAsr,
+ deferred ? aSc.GetDeferredTransformMatrix() : Nothing(),
+ deferredId);
+ }
+ }
+ }
+ } while (iter.HasNext());
+
+ mDumpIndent--;
+ if (aNewClipList) {
+ mClipManager.EndList(aSc);
+ }
+}
+
+void WebRenderCommandBuilder::PushOverrideForASR(
+ const ActiveScrolledRoot* aASR, const wr::WrSpatialId& aSpatialId) {
+ mClipManager.PushOverrideForASR(aASR, aSpatialId);
+}
+
+void WebRenderCommandBuilder::PopOverrideForASR(
+ const ActiveScrolledRoot* aASR) {
+ mClipManager.PopOverrideForASR(aASR);
+}
+
+static wr::WrRotation ToWrRotation(VideoRotation aRotation) {
+ switch (aRotation) {
+ case VideoRotation::kDegree_0:
+ return wr::WrRotation::Degree0;
+ case VideoRotation::kDegree_90:
+ return wr::WrRotation::Degree90;
+ case VideoRotation::kDegree_180:
+ return wr::WrRotation::Degree180;
+ case VideoRotation::kDegree_270:
+ return wr::WrRotation::Degree270;
+ }
+ return wr::WrRotation::Degree0;
+}
+
+Maybe<wr::ImageKey> WebRenderCommandBuilder::CreateImageKey(
+ nsDisplayItem* aItem, ImageContainer* aContainer,
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ mozilla::wr::ImageRendering aRendering, const StackingContextHelper& aSc,
+ gfx::IntSize& aSize, const Maybe<LayoutDeviceRect>& aAsyncImageBounds) {
+ RefPtr<WebRenderImageData> imageData =
+ CreateOrRecycleWebRenderUserData<WebRenderImageData>(aItem);
+ MOZ_ASSERT(imageData);
+
+ if (aContainer->IsAsync()) {
+ MOZ_ASSERT(aAsyncImageBounds);
+
+ LayoutDeviceRect rect = aAsyncImageBounds.value();
+ LayoutDeviceRect scBounds(LayoutDevicePoint(0, 0), rect.Size());
+ // TODO!
+ // We appear to be using the image bridge for a lot (most/all?) of
+ // layers-free image handling and that breaks frame consistency.
+ imageData->CreateAsyncImageWebRenderCommands(
+ aBuilder, aContainer, aSc, rect, scBounds,
+ ToWrRotation(aContainer->GetRotation()), aRendering,
+ wr::MixBlendMode::Normal, !aItem->BackfaceIsHidden());
+ return Nothing();
+ }
+
+ AutoLockImage autoLock(aContainer);
+ if (!autoLock.HasImage()) {
+ return Nothing();
+ }
+ mozilla::layers::Image* image = autoLock.GetImage();
+ aSize = image->GetSize();
+
+ return imageData->UpdateImageKey(aContainer, aResources);
+}
+
+bool WebRenderCommandBuilder::PushImage(
+ nsDisplayItem* aItem, ImageContainer* aContainer,
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc, const LayoutDeviceRect& aRect,
+ const LayoutDeviceRect& aClip) {
+ auto rendering = wr::ToImageRendering(aItem->Frame()->UsedImageRendering());
+ gfx::IntSize size;
+ Maybe<wr::ImageKey> key =
+ CreateImageKey(aItem, aContainer, aBuilder, aResources, rendering, aSc,
+ size, Some(aRect));
+ if (aContainer->IsAsync()) {
+ // Async ImageContainer does not create ImageKey, instead it uses Pipeline.
+ MOZ_ASSERT(key.isNothing());
+ return true;
+ }
+ if (!key) {
+ return false;
+ }
+
+ auto r = wr::ToLayoutRect(aRect);
+ auto c = wr::ToLayoutRect(aClip);
+ aBuilder.PushImage(r, c, !aItem->BackfaceIsHidden(), false, rendering,
+ key.value());
+
+ return true;
+}
+
+Maybe<wr::ImageKey> WebRenderCommandBuilder::CreateImageProviderKey(
+ nsDisplayItem* aItem, image::WebRenderImageProvider* aProvider,
+ image::ImgDrawResult aDrawResult,
+ mozilla::wr::IpcResourceUpdateQueue& aResources) {
+ RefPtr<WebRenderImageProviderData> imageData =
+ CreateOrRecycleWebRenderUserData<WebRenderImageProviderData>(aItem);
+ MOZ_ASSERT(imageData);
+ return imageData->UpdateImageKey(aProvider, aDrawResult, aResources);
+}
+
+bool WebRenderCommandBuilder::PushImageProvider(
+ nsDisplayItem* aItem, image::WebRenderImageProvider* aProvider,
+ image::ImgDrawResult aDrawResult, mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const LayoutDeviceRect& aRect, const LayoutDeviceRect& aClip) {
+ Maybe<wr::ImageKey> key =
+ CreateImageProviderKey(aItem, aProvider, aDrawResult, aResources);
+ if (!key) {
+ return false;
+ }
+
+ bool antialiased = aItem->GetType() == DisplayItemType::TYPE_SVG_GEOMETRY;
+
+ auto rendering = wr::ToImageRendering(aItem->Frame()->UsedImageRendering());
+ auto r = wr::ToLayoutRect(aRect);
+ auto c = wr::ToLayoutRect(aClip);
+ aBuilder.PushImage(r, c, !aItem->BackfaceIsHidden(), antialiased, rendering,
+ key.value());
+
+ return true;
+}
+
+static void PaintItemByDrawTarget(nsDisplayItem* aItem, gfx::DrawTarget* aDT,
+ const LayoutDevicePoint& aOffset,
+ const IntRect& visibleRect,
+ nsDisplayListBuilder* aDisplayListBuilder,
+ const gfx::MatrixScales& aScale,
+ Maybe<gfx::DeviceColor>& aHighlight) {
+ MOZ_ASSERT(aDT && aDT->IsValid());
+
+ // XXX Why is this ClearRect() needed?
+ aDT->ClearRect(Rect(visibleRect));
+ gfxContext context(aDT);
+
+ switch (aItem->GetType()) {
+ case DisplayItemType::TYPE_SVG_WRAPPER:
+ case DisplayItemType::TYPE_MASK: {
+ // These items should be handled by other code paths
+ MOZ_RELEASE_ASSERT(0);
+ break;
+ }
+ default:
+ if (!aItem->AsPaintedDisplayItem()) {
+ break;
+ }
+
+ context.SetMatrix(context.CurrentMatrix().PreScale(aScale).PreTranslate(
+ -aOffset.x, -aOffset.y));
+ if (aDisplayListBuilder->IsPaintingToWindow()) {
+ aItem->Frame()->AddStateBits(NS_FRAME_PAINTED_THEBES);
+ }
+ aItem->AsPaintedDisplayItem()->Paint(aDisplayListBuilder, &context);
+ break;
+ }
+
+ if (aHighlight && aItem->GetType() != DisplayItemType::TYPE_MASK) {
+ // Apply highlight fills, if the appropriate prefs are set.
+ // We don't do this for masks because we'd be filling the A8 mask surface,
+ // which isn't very useful.
+ aDT->SetTransform(gfx::Matrix());
+ aDT->FillRect(Rect(visibleRect), gfx::ColorPattern(aHighlight.value()));
+ }
+}
+
+bool WebRenderCommandBuilder::ComputeInvalidationForDisplayItem(
+ nsDisplayListBuilder* aBuilder, const nsPoint& aShift,
+ nsDisplayItem* aItem) {
+ RefPtr<WebRenderFallbackData> fallbackData =
+ CreateOrRecycleWebRenderUserData<WebRenderFallbackData>(aItem);
+
+ nsRect invalid;
+ if (!fallbackData->mGeometry || aItem->IsInvalid(invalid)) {
+ fallbackData->mGeometry = WrapUnique(aItem->AllocateGeometry(aBuilder));
+ return true;
+ }
+
+ fallbackData->mGeometry->MoveBy(aShift);
+ nsRegion combined;
+ aItem->ComputeInvalidationRegion(aBuilder, fallbackData->mGeometry.get(),
+ &combined);
+
+ UniquePtr<nsDisplayItemGeometry> geometry;
+ if (!combined.IsEmpty() || aItem->NeedsGeometryUpdates()) {
+ geometry = WrapUnique(aItem->AllocateGeometry(aBuilder));
+ }
+
+ fallbackData->mClip.AddOffsetAndComputeDifference(
+ aShift, fallbackData->mGeometry->ComputeInvalidationRegion(),
+ aItem->GetClip(),
+ geometry ? geometry->ComputeInvalidationRegion()
+ : fallbackData->mGeometry->ComputeInvalidationRegion(),
+ &combined);
+
+ if (geometry) {
+ fallbackData->mGeometry = std::move(geometry);
+ }
+ fallbackData->mClip = aItem->GetClip();
+
+ if (!combined.IsEmpty()) {
+ return true;
+ } else if (aItem->GetChildren()) {
+ return ComputeInvalidationForDisplayList(aBuilder, aShift,
+ aItem->GetChildren());
+ }
+ return false;
+}
+
+bool WebRenderCommandBuilder::ComputeInvalidationForDisplayList(
+ nsDisplayListBuilder* aBuilder, const nsPoint& aShift,
+ nsDisplayList* aList) {
+ FlattenedDisplayListIterator iter(aBuilder, aList);
+ while (iter.HasNext()) {
+ if (ComputeInvalidationForDisplayItem(aBuilder, aShift,
+ iter.GetNextItem())) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// When drawing fallback images we create either
+// a real image or a blob image that will contain the display item.
+// In the case of a blob image we paint the item at 0,0 instead
+// of trying to keep at aItem->GetBounds().TopLeft() like we do
+// with SVG. We do this because there's not necessarily a reference frame
+// between us and the rest of the world so the the coordinates
+// that we get for the bounds are not necessarily stable across scrolling
+// or other movement.
+already_AddRefed<WebRenderFallbackData>
+WebRenderCommandBuilder::GenerateFallbackData(
+ nsDisplayItem* aItem, wr::DisplayListBuilder& aBuilder,
+ wr::IpcResourceUpdateQueue& aResources, const StackingContextHelper& aSc,
+ nsDisplayListBuilder* aDisplayListBuilder, LayoutDeviceRect& aImageRect) {
+ bool useBlobImage = aItem->ShouldUseBlobRenderingForFallback();
+ Maybe<gfx::DeviceColor> highlight = Nothing();
+ if (StaticPrefs::gfx_webrender_debug_highlight_painted_layers()) {
+ highlight = Some(useBlobImage ? gfx::DeviceColor(1.0, 0.0, 0.0, 0.5)
+ : gfx::DeviceColor(1.0, 1.0, 0.0, 0.5));
+ }
+
+ RefPtr<WebRenderFallbackData> fallbackData =
+ CreateOrRecycleWebRenderUserData<WebRenderFallbackData>(aItem);
+
+ // Blob images will only draw the visible area of the blob so we don't need to
+ // clip them here and can just rely on the webrender clipping.
+ // TODO We also don't clip native themed widget to avoid over-invalidation
+ // during scrolling. It would be better to support a sort of streaming/tiling
+ // scheme for large ones but the hope is that we should not have large native
+ // themed items.
+ bool snap;
+ nsRect paintBounds = aItem->GetBounds(aDisplayListBuilder, &snap);
+ nsRect buildingRect = aItem->GetBuildingRect();
+
+ const int32_t appUnitsPerDevPixel =
+ aItem->Frame()->PresContext()->AppUnitsPerDevPixel();
+ auto bounds =
+ LayoutDeviceRect::FromAppUnits(paintBounds, appUnitsPerDevPixel);
+ if (bounds.IsEmpty()) {
+ return nullptr;
+ }
+
+ MatrixScales scale = aSc.GetInheritedScale();
+ MatrixScales oldScale = fallbackData->mScale;
+ // We tolerate slight changes in scale so that we don't, for example,
+ // rerasterize on MotionMark
+ bool differentScale = gfx::FuzzyEqual(scale.xScale, oldScale.xScale, 1e-6f) &&
+ gfx::FuzzyEqual(scale.yScale, oldScale.yScale, 1e-6f);
+
+ auto layerScale = LayoutDeviceToLayerScale2D::FromUnknownScale(scale);
+
+ auto trans =
+ ViewAs<LayerPixel>(aSc.GetSnappingSurfaceTransform().GetTranslation());
+
+ if (!FitsInt32(trans.X()) || !FitsInt32(trans.Y())) {
+ // The translation overflowed int32_t.
+ return nullptr;
+ }
+
+ auto snappedTrans = LayerIntPoint::Floor(trans);
+ LayerPoint residualOffset = trans - snappedTrans;
+
+ nsRegion opaqueRegion = aItem->GetOpaqueRegion(aDisplayListBuilder, &snap);
+ wr::OpacityType opacity = opaqueRegion.Contains(paintBounds)
+ ? wr::OpacityType::Opaque
+ : wr::OpacityType::HasAlphaChannel;
+
+ LayerIntRect dtRect, visibleRect;
+ // If we think the item is opaque we round the bounds
+ // to the nearest pixel instead of rounding them out. If we rounded
+ // out we'd potentially introduce transparent pixels.
+ //
+ // Ideally we'd be able to ask an item its bounds in pixels and whether
+ // they're all opaque. Unfortunately no such API exists so we currently
+ // just hope that we get it right.
+ if (aBuilder.GetInheritedOpacity() == 1.0f &&
+ opacity == wr::OpacityType::Opaque && snap) {
+ dtRect = LayerIntRect::FromUnknownRect(
+ ScaleToNearestPixelsOffset(paintBounds, scale.xScale, scale.yScale,
+ appUnitsPerDevPixel, residualOffset));
+
+ visibleRect =
+ LayerIntRect::FromUnknownRect(
+ ScaleToNearestPixelsOffset(buildingRect, scale.xScale, scale.yScale,
+ appUnitsPerDevPixel, residualOffset))
+ .Intersect(dtRect);
+ } else {
+ dtRect = ScaleToOutsidePixelsOffset(paintBounds, scale.xScale, scale.yScale,
+ appUnitsPerDevPixel, residualOffset);
+
+ visibleRect =
+ ScaleToOutsidePixelsOffset(buildingRect, scale.xScale, scale.yScale,
+ appUnitsPerDevPixel, residualOffset)
+ .Intersect(dtRect);
+ }
+
+ auto visibleSize = visibleRect.Size();
+ // these rectangles can overflow from scaling so try to
+ // catch that with IsEmpty() checks. See bug 1622126.
+ if (visibleSize.IsEmpty() || dtRect.IsEmpty()) {
+ return nullptr;
+ }
+
+ if (useBlobImage) {
+ // Display item bounds should be unscaled
+ aImageRect = visibleRect / layerScale;
+ } else {
+ // Display item bounds should be unscaled
+ aImageRect = dtRect / layerScale;
+ }
+
+ // We always paint items at 0,0 so the visibleRect that we use inside the blob
+ // is needs to be adjusted by the display item bounds top left.
+ visibleRect -= dtRect.TopLeft();
+
+ nsDisplayItemGeometry* geometry = fallbackData->mGeometry.get();
+
+ bool needPaint = true;
+
+ MOZ_RELEASE_ASSERT(aItem->GetType() != DisplayItemType::TYPE_SVG_WRAPPER);
+ if (geometry && !fallbackData->IsInvalid() &&
+ aItem->GetType() != DisplayItemType::TYPE_SVG_WRAPPER && differentScale) {
+ nsRect invalid;
+ if (!aItem->IsInvalid(invalid)) {
+ nsPoint shift = paintBounds.TopLeft() - geometry->mBounds.TopLeft();
+ geometry->MoveBy(shift);
+
+ nsRegion invalidRegion;
+ aItem->ComputeInvalidationRegion(aDisplayListBuilder, geometry,
+ &invalidRegion);
+
+ nsRect lastBounds = fallbackData->mBounds;
+ lastBounds.MoveBy(shift);
+
+ if (lastBounds.IsEqualInterior(paintBounds) && invalidRegion.IsEmpty() &&
+ aBuilder.GetInheritedOpacity() == fallbackData->mOpacity) {
+ if (aItem->GetType() == DisplayItemType::TYPE_FILTER) {
+ needPaint = ComputeInvalidationForDisplayList(
+ aDisplayListBuilder, shift, aItem->GetChildren());
+ if (!buildingRect.IsEqualInterior(fallbackData->mBuildingRect)) {
+ needPaint = true;
+ }
+ } else {
+ needPaint = false;
+ }
+ }
+ }
+ }
+
+ if (needPaint || !fallbackData->GetImageKey()) {
+ fallbackData->mGeometry =
+ WrapUnique(aItem->AllocateGeometry(aDisplayListBuilder));
+
+ gfx::SurfaceFormat format = aItem->GetType() == DisplayItemType::TYPE_MASK
+ ? gfx::SurfaceFormat::A8
+ : (opacity == wr::OpacityType::Opaque
+ ? gfx::SurfaceFormat::B8G8R8X8
+ : gfx::SurfaceFormat::B8G8R8A8);
+ if (useBlobImage) {
+ MOZ_ASSERT(!opaqueRegion.IsComplex());
+
+ std::vector<RefPtr<ScaledFont>> fonts;
+ bool validFonts = true;
+ RefPtr<WebRenderDrawEventRecorder> recorder =
+ MakeAndAddRef<WebRenderDrawEventRecorder>(
+ [&](MemStream& aStream,
+ std::vector<RefPtr<ScaledFont>>& aScaledFonts) {
+ size_t count = aScaledFonts.size();
+ aStream.write((const char*)&count, sizeof(count));
+ for (auto& scaled : aScaledFonts) {
+ Maybe<wr::FontInstanceKey> key =
+ mManager->WrBridge()->GetFontKeyForScaledFont(scaled,
+ aResources);
+ if (key.isNothing()) {
+ validFonts = false;
+ break;
+ }
+ BlobFont font = {key.value(), scaled};
+ aStream.write((const char*)&font, sizeof(font));
+ }
+ fonts = std::move(aScaledFonts);
+ });
+ RefPtr<gfx::DrawTarget> dummyDt = gfx::Factory::CreateDrawTarget(
+ gfx::BackendType::SKIA, gfx::IntSize(1, 1), format);
+ RefPtr<gfx::DrawTarget> dt = gfx::Factory::CreateRecordingDrawTarget(
+ recorder, dummyDt, (dtRect - dtRect.TopLeft()).ToUnknownRect());
+ if (aBuilder.GetInheritedOpacity() != 1.0f) {
+ dt->PushLayer(false, aBuilder.GetInheritedOpacity(), nullptr,
+ gfx::Matrix());
+ }
+ PaintItemByDrawTarget(aItem, dt, (dtRect / layerScale).TopLeft(),
+ /*aVisibleRect: */ dt->GetRect(),
+ aDisplayListBuilder, scale, highlight);
+ if (aBuilder.GetInheritedOpacity() != 1.0f) {
+ dt->PopLayer();
+ }
+
+ // the item bounds are relative to the blob origin which is
+ // dtRect.TopLeft()
+ recorder->FlushItem((dtRect - dtRect.TopLeft()).ToUnknownRect());
+ recorder->Finish();
+
+ if (!validFonts) {
+ gfxCriticalNote << "Failed serializing fonts for blob image";
+ return nullptr;
+ }
+
+ Range<uint8_t> bytes((uint8_t*)recorder->mOutputStream.mData,
+ recorder->mOutputStream.mLength);
+ wr::BlobImageKey key =
+ wr::BlobImageKey{mManager->WrBridge()->GetNextImageKey()};
+ wr::ImageDescriptor descriptor(visibleSize.ToUnknownSize(), 0,
+ dt->GetFormat(), opacity);
+ if (!aResources.AddBlobImage(
+ key, descriptor, bytes,
+ ViewAs<ImagePixel>(visibleRect,
+ PixelCastJustification::LayerIsImage))) {
+ return nullptr;
+ }
+ TakeExternalSurfaces(recorder, fallbackData->mExternalSurfaces,
+ mManager->GetRenderRootStateManager(), aResources);
+ fallbackData->SetBlobImageKey(key);
+ fallbackData->SetFonts(fonts);
+ } else {
+ WebRenderImageData* imageData = fallbackData->PaintIntoImage();
+
+ imageData->CreateImageClientIfNeeded();
+ RefPtr<ImageClient> imageClient = imageData->GetImageClient();
+ RefPtr<ImageContainer> imageContainer = MakeAndAddRef<ImageContainer>();
+
+ {
+ UpdateImageHelper helper(imageContainer, imageClient,
+ dtRect.Size().ToUnknownSize(), format);
+ {
+ RefPtr<gfx::DrawTarget> dt = helper.GetDrawTarget();
+ if (!dt) {
+ return nullptr;
+ }
+ if (aBuilder.GetInheritedOpacity() != 1.0f) {
+ dt->PushLayer(false, aBuilder.GetInheritedOpacity(), nullptr,
+ gfx::Matrix());
+ }
+ PaintItemByDrawTarget(aItem, dt,
+ /*aOffset: */ aImageRect.TopLeft(),
+ /*aVisibleRect: */ dt->GetRect(),
+ aDisplayListBuilder, scale, highlight);
+ if (aBuilder.GetInheritedOpacity() != 1.0f) {
+ dt->PopLayer();
+ }
+ }
+
+ // Update image if there it's invalidated.
+ if (!helper.UpdateImage()) {
+ return nullptr;
+ }
+ }
+
+ // Force update the key in fallback data since we repaint the image in
+ // this path. If not force update, fallbackData may reuse the original key
+ // because it doesn't know UpdateImageHelper already updated the image
+ // container.
+ if (!imageData->UpdateImageKey(imageContainer, aResources, true)) {
+ return nullptr;
+ }
+ }
+
+ fallbackData->mScale = scale;
+ fallbackData->mOpacity = aBuilder.GetInheritedOpacity();
+ fallbackData->SetInvalid(false);
+ }
+
+ if (useBlobImage) {
+ MOZ_DIAGNOSTIC_ASSERT(mManager->WrBridge()->MatchesNamespace(
+ fallbackData->GetBlobImageKey().ref()),
+ "Stale blob key for fallback!");
+
+ aResources.SetBlobImageVisibleArea(
+ fallbackData->GetBlobImageKey().value(),
+ ViewAs<ImagePixel>(visibleRect, PixelCastJustification::LayerIsImage));
+ }
+
+ // Update current bounds to fallback data
+ fallbackData->mBounds = paintBounds;
+ fallbackData->mBuildingRect = buildingRect;
+
+ MOZ_ASSERT(fallbackData->GetImageKey());
+
+ return fallbackData.forget();
+}
+
+void WebRenderMaskData::ClearImageKey() {
+ if (mBlobKey) {
+ mManager->AddBlobImageKeyForDiscard(mBlobKey.value());
+ }
+ mBlobKey.reset();
+}
+
+void WebRenderMaskData::Invalidate() {
+ mMaskStyle = nsStyleImageLayers(nsStyleImageLayers::LayerType::Mask);
+}
+
+Maybe<wr::ImageMask> WebRenderCommandBuilder::BuildWrMaskImage(
+ nsDisplayMasksAndClipPaths* aMaskItem, wr::DisplayListBuilder& aBuilder,
+ wr::IpcResourceUpdateQueue& aResources, const StackingContextHelper& aSc,
+ nsDisplayListBuilder* aDisplayListBuilder,
+ const LayoutDeviceRect& aBounds) {
+ RefPtr<WebRenderMaskData> maskData =
+ CreateOrRecycleWebRenderUserData<WebRenderMaskData>(aMaskItem);
+
+ if (!maskData) {
+ return Nothing();
+ }
+
+ bool snap;
+ nsRect bounds = aMaskItem->GetBounds(aDisplayListBuilder, &snap);
+
+ const int32_t appUnitsPerDevPixel =
+ aMaskItem->Frame()->PresContext()->AppUnitsPerDevPixel();
+
+ MatrixScales scale = aSc.GetInheritedScale();
+ MatrixScales oldScale = maskData->mScale;
+ // This scale determination should probably be done using
+ // ChooseScaleAndSetTransform but for now we just fake it.
+ // We tolerate slight changes in scale so that we don't, for example,
+ // rerasterize on MotionMark
+ bool sameScale = FuzzyEqual(scale.xScale, oldScale.xScale, 1e-6f) &&
+ FuzzyEqual(scale.yScale, oldScale.yScale, 1e-6f);
+
+ LayerIntRect itemRect =
+ LayerIntRect::FromUnknownRect(bounds.ScaleToOutsidePixels(
+ scale.xScale, scale.yScale, appUnitsPerDevPixel));
+
+ LayerIntRect visibleRect =
+ LayerIntRect::FromUnknownRect(
+ aMaskItem->GetBuildingRect().ScaleToOutsidePixels(
+ scale.xScale, scale.yScale, appUnitsPerDevPixel))
+ .SafeIntersect(itemRect);
+
+ if (visibleRect.IsEmpty()) {
+ return Nothing();
+ }
+
+ LayoutDeviceToLayerScale2D layerScale(scale.xScale, scale.yScale);
+ LayoutDeviceRect imageRect = LayerRect(visibleRect) / layerScale;
+
+ nsPoint maskOffset = aMaskItem->ToReferenceFrame() - bounds.TopLeft();
+
+ bool shouldHandleOpacity = aBuilder.GetInheritedOpacity() != 1.0f;
+
+ nsRect dirtyRect;
+ // If this mask item is being painted for the first time, some members of
+ // WebRenderMaskData are still default initialized. This is intentional.
+ if (aMaskItem->IsInvalid(dirtyRect) ||
+ !itemRect.IsEqualInterior(maskData->mItemRect) ||
+ !(aMaskItem->Frame()->StyleSVGReset()->mMask == maskData->mMaskStyle) ||
+ maskOffset != maskData->mMaskOffset || !sameScale ||
+ shouldHandleOpacity != maskData->mShouldHandleOpacity) {
+ IntSize size = itemRect.Size().ToUnknownSize();
+
+ if (!Factory::AllowedSurfaceSize(size)) {
+ return Nothing();
+ }
+
+ std::vector<RefPtr<ScaledFont>> fonts;
+ bool validFonts = true;
+ RefPtr<WebRenderDrawEventRecorder> recorder =
+ MakeAndAddRef<WebRenderDrawEventRecorder>(
+ [&](MemStream& aStream,
+ std::vector<RefPtr<ScaledFont>>& aScaledFonts) {
+ size_t count = aScaledFonts.size();
+ aStream.write((const char*)&count, sizeof(count));
+
+ for (auto& scaled : aScaledFonts) {
+ Maybe<wr::FontInstanceKey> key =
+ mManager->WrBridge()->GetFontKeyForScaledFont(scaled,
+ aResources);
+ if (key.isNothing()) {
+ validFonts = false;
+ break;
+ }
+ BlobFont font = {key.value(), scaled};
+ aStream.write((const char*)&font, sizeof(font));
+ }
+
+ fonts = std::move(aScaledFonts);
+ });
+
+ RefPtr<DrawTarget> dummyDt = Factory::CreateDrawTarget(
+ BackendType::SKIA, IntSize(1, 1), SurfaceFormat::A8);
+ RefPtr<DrawTarget> dt = Factory::CreateRecordingDrawTarget(
+ recorder, dummyDt, IntRect(IntPoint(0, 0), size));
+ if (!dt || !dt->IsValid()) {
+ gfxCriticalNote << "Failed to create drawTarget for blob mask image";
+ return Nothing();
+ }
+
+ gfxContext context(dt);
+ context.SetMatrix(context.CurrentMatrix()
+ .PreTranslate(-itemRect.x, -itemRect.y)
+ .PreScale(scale));
+
+ bool maskPainted = false;
+ bool maskIsComplete = aMaskItem->PaintMask(
+ aDisplayListBuilder, &context, shouldHandleOpacity, &maskPainted);
+ if (!maskPainted) {
+ return Nothing();
+ }
+
+ // If a mask is incomplete or missing (e.g. it's display: none) the proper
+ // behaviour depends on the masked frame being html or svg.
+ //
+ // For an HTML frame:
+ // According to css-masking spec, always create a mask surface when
+ // we have any item in maskFrame even if all of those items are
+ // non-resolvable <mask-sources> or <images> so continue with the
+ // painting code. Note that in a common case of no layer of the mask being
+ // complete or even partially complete then the mask surface will be
+ // transparent black so this results in hiding the frame.
+ // For an SVG frame:
+ // SVG 1.1 say that if we fail to resolve a mask, we should draw the
+ // object unmasked so return Nothing().
+ if (!maskIsComplete &&
+ aMaskItem->Frame()->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
+ return Nothing();
+ }
+
+ recorder->FlushItem(IntRect(0, 0, size.width, size.height));
+ recorder->Finish();
+
+ if (!validFonts) {
+ gfxCriticalNote << "Failed serializing fonts for blob mask image";
+ return Nothing();
+ }
+
+ Range<uint8_t> bytes((uint8_t*)recorder->mOutputStream.mData,
+ recorder->mOutputStream.mLength);
+ wr::BlobImageKey key =
+ wr::BlobImageKey{mManager->WrBridge()->GetNextImageKey()};
+ wr::ImageDescriptor descriptor(size, 0, dt->GetFormat(),
+ wr::OpacityType::HasAlphaChannel);
+ if (!aResources.AddBlobImage(key, descriptor, bytes,
+ ImageIntRect(0, 0, size.width, size.height))) {
+ return Nothing();
+ }
+ maskData->ClearImageKey();
+ maskData->mBlobKey = Some(key);
+ maskData->mFonts = fonts;
+ TakeExternalSurfaces(recorder, maskData->mExternalSurfaces,
+ mManager->GetRenderRootStateManager(), aResources);
+ if (maskIsComplete) {
+ maskData->mItemRect = itemRect;
+ maskData->mMaskOffset = maskOffset;
+ maskData->mScale = scale;
+ maskData->mMaskStyle = aMaskItem->Frame()->StyleSVGReset()->mMask;
+ maskData->mShouldHandleOpacity = shouldHandleOpacity;
+ }
+ }
+
+ aResources.SetBlobImageVisibleArea(
+ maskData->mBlobKey.value(),
+ ViewAs<ImagePixel>(visibleRect - itemRect.TopLeft(),
+ PixelCastJustification::LayerIsImage));
+
+ MOZ_DIAGNOSTIC_ASSERT(
+ mManager->WrBridge()->MatchesNamespace(maskData->mBlobKey.ref()),
+ "Stale blob key for mask!");
+
+ wr::ImageMask imageMask;
+ imageMask.image = wr::AsImageKey(maskData->mBlobKey.value());
+ imageMask.rect = wr::ToLayoutRect(imageRect);
+ return Some(imageMask);
+}
+
+bool WebRenderCommandBuilder::PushItemAsImage(
+ nsDisplayItem* aItem, wr::DisplayListBuilder& aBuilder,
+ wr::IpcResourceUpdateQueue& aResources, const StackingContextHelper& aSc,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ LayoutDeviceRect imageRect;
+ RefPtr<WebRenderFallbackData> fallbackData = GenerateFallbackData(
+ aItem, aBuilder, aResources, aSc, aDisplayListBuilder, imageRect);
+ if (!fallbackData) {
+ return false;
+ }
+
+ wr::LayoutRect dest = wr::ToLayoutRect(imageRect);
+ auto rendering = wr::ToImageRendering(aItem->Frame()->UsedImageRendering());
+ aBuilder.PushImage(dest, dest, !aItem->BackfaceIsHidden(), false, rendering,
+ fallbackData->GetImageKey().value());
+ return true;
+}
+
+void WebRenderCommandBuilder::RemoveUnusedAndResetWebRenderUserData() {
+ mWebRenderUserDatas.RemoveIf([&](WebRenderUserData* data) {
+ if (!data->IsUsed()) {
+ nsIFrame* frame = data->GetFrame();
+
+ MOZ_ASSERT(frame->HasProperty(WebRenderUserDataProperty::Key()));
+
+ WebRenderUserDataTable* userDataTable =
+ frame->GetProperty(WebRenderUserDataProperty::Key());
+
+ MOZ_ASSERT(userDataTable->Count());
+
+ userDataTable->Remove(
+ WebRenderUserDataKey(data->GetDisplayItemKey(), data->GetType()));
+
+ if (!userDataTable->Count()) {
+ frame->RemoveProperty(WebRenderUserDataProperty::Key());
+ userDataTable = nullptr;
+ }
+
+ switch (data->GetType()) {
+ case WebRenderUserData::UserDataType::eCanvas:
+ mLastCanvasDatas.Remove(data->AsCanvasData());
+ break;
+ case WebRenderUserData::UserDataType::eAnimation:
+ EffectCompositor::ClearIsRunningOnCompositor(
+ frame, GetDisplayItemTypeFromKey(data->GetDisplayItemKey()));
+ break;
+ default:
+ break;
+ }
+
+ return true;
+ }
+
+ data->SetUsed(false);
+ return false;
+ });
+}
+
+void WebRenderCommandBuilder::ClearCachedResources() {
+ RemoveUnusedAndResetWebRenderUserData();
+ // UserDatas should only be in the used state during a call to
+ // WebRenderCommandBuilder::BuildWebRenderCommands The should always be false
+ // upon return from BuildWebRenderCommands().
+ MOZ_RELEASE_ASSERT(mWebRenderUserDatas.Count() == 0);
+}
+
+WebRenderGroupData::WebRenderGroupData(
+ RenderRootStateManager* aRenderRootStateManager, nsDisplayItem* aItem)
+ : WebRenderUserData(aRenderRootStateManager, aItem) {
+ MOZ_COUNT_CTOR(WebRenderGroupData);
+}
+
+WebRenderGroupData::~WebRenderGroupData() {
+ MOZ_COUNT_DTOR(WebRenderGroupData);
+ GP("Group data destruct\n");
+ mSubGroup.ClearImageKey(mManager, true);
+ mFollowingGroup.ClearImageKey(mManager, true);
+}
+
+} // namespace layers
+} // namespace mozilla
diff --git a/gfx/layers/wr/WebRenderCommandBuilder.h b/gfx/layers/wr/WebRenderCommandBuilder.h
new file mode 100644
index 0000000000..68c9a4ce63
--- /dev/null
+++ b/gfx/layers/wr/WebRenderCommandBuilder.h
@@ -0,0 +1,237 @@
+/* -*- 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/. */
+
+#ifndef GFX_WEBRENDERCOMMANDBUILDER_H
+#define GFX_WEBRENDERCOMMANDBUILDER_H
+
+#include "mozilla/webrender/WebRenderAPI.h"
+#include "mozilla/layers/ClipManager.h"
+#include "mozilla/layers/HitTestInfoManager.h"
+#include "mozilla/layers/WebRenderMessages.h"
+#include "mozilla/layers/WebRenderScrollData.h"
+#include "mozilla/layers/WebRenderUserData.h"
+#include "mozilla/SVGIntegrationUtils.h" // for WrFiltersHolder
+#include "nsDisplayList.h"
+#include "nsIFrame.h"
+#include "nsTHashSet.h"
+#include "DisplayItemCache.h"
+#include "ImgDrawResult.h"
+
+namespace mozilla {
+
+namespace image {
+class WebRenderImageProvider;
+}
+
+namespace layers {
+
+class ImageClient;
+class ImageContainer;
+class WebRenderBridgeChild;
+class WebRenderCanvasData;
+class WebRenderCanvasRendererAsync;
+class WebRenderImageData;
+class WebRenderFallbackData;
+class WebRenderParentCommand;
+class WebRenderUserData;
+
+class WebRenderCommandBuilder final {
+ typedef nsTHashSet<RefPtr<WebRenderUserData>> WebRenderUserDataRefTable;
+ typedef nsTHashSet<RefPtr<WebRenderCanvasData>> CanvasDataSet;
+
+ public:
+ explicit WebRenderCommandBuilder(WebRenderLayerManager* aManager);
+
+ void Destroy();
+
+ void EmptyTransaction();
+
+ bool NeedsEmptyTransaction();
+
+ void BuildWebRenderCommands(wr::DisplayListBuilder& aBuilder,
+ wr::IpcResourceUpdateQueue& aResourceUpdates,
+ nsDisplayList* aDisplayList,
+ nsDisplayListBuilder* aDisplayListBuilder,
+ WebRenderScrollData& aScrollData,
+ WrFiltersHolder&& aFilters);
+
+ void PushOverrideForASR(const ActiveScrolledRoot* aASR,
+ const wr::WrSpatialId& aSpatialId);
+ void PopOverrideForASR(const ActiveScrolledRoot* aASR);
+
+ Maybe<wr::ImageKey> CreateImageKey(
+ nsDisplayItem* aItem, ImageContainer* aContainer,
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ mozilla::wr::ImageRendering aRendering, const StackingContextHelper& aSc,
+ gfx::IntSize& aSize, const Maybe<LayoutDeviceRect>& aAsyncImageBounds);
+
+ Maybe<wr::ImageKey> CreateImageProviderKey(
+ nsDisplayItem* aItem, image::WebRenderImageProvider* aProvider,
+ image::ImgDrawResult aDrawResult,
+ mozilla::wr::IpcResourceUpdateQueue& aResources);
+
+ WebRenderUserDataRefTable* GetWebRenderUserDataTable() {
+ return &mWebRenderUserDatas;
+ }
+
+ bool PushImage(nsDisplayItem* aItem, ImageContainer* aContainer,
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ const LayoutDeviceRect& aRect, const LayoutDeviceRect& aClip);
+
+ bool PushImageProvider(nsDisplayItem* aItem,
+ image::WebRenderImageProvider* aProvider,
+ image::ImgDrawResult aDrawResult,
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const LayoutDeviceRect& aRect,
+ const LayoutDeviceRect& aClip);
+
+ Maybe<wr::ImageMask> BuildWrMaskImage(
+ nsDisplayMasksAndClipPaths* aMaskItem, wr::DisplayListBuilder& aBuilder,
+ wr::IpcResourceUpdateQueue& aResources, const StackingContextHelper& aSc,
+ nsDisplayListBuilder* aDisplayListBuilder,
+ const LayoutDeviceRect& aBounds);
+
+ bool PushItemAsImage(nsDisplayItem* aItem, wr::DisplayListBuilder& aBuilder,
+ wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ nsDisplayListBuilder* aDisplayListBuilder);
+
+ void CreateWebRenderCommandsFromDisplayList(
+ nsDisplayList* aDisplayList, nsDisplayItem* aWrappingItem,
+ nsDisplayListBuilder* aDisplayListBuilder,
+ const StackingContextHelper& aSc, wr::DisplayListBuilder& aBuilder,
+ wr::IpcResourceUpdateQueue& aResources, bool aNewClipList = true);
+
+ // aWrappingItem has to be non-null.
+ void DoGroupingForDisplayList(nsDisplayList* aDisplayList,
+ nsDisplayItem* aWrappingItem,
+ nsDisplayListBuilder* aDisplayListBuilder,
+ const StackingContextHelper& aSc,
+ wr::DisplayListBuilder& aBuilder,
+ wr::IpcResourceUpdateQueue& aResources);
+
+ already_AddRefed<WebRenderFallbackData> GenerateFallbackData(
+ nsDisplayItem* aItem, wr::DisplayListBuilder& aBuilder,
+ wr::IpcResourceUpdateQueue& aResources, const StackingContextHelper& aSc,
+ nsDisplayListBuilder* aDisplayListBuilder, LayoutDeviceRect& aImageRect);
+
+ void RemoveUnusedAndResetWebRenderUserData();
+ void ClearCachedResources();
+
+ bool ShouldDumpDisplayList(nsDisplayListBuilder* aBuilder);
+ wr::usize GetBuilderDumpIndex() const { return mBuilderDumpIndex; }
+
+ bool GetContainsSVGGroup() { return mContainsSVGGroup; }
+
+ // Those are data that we kept between transactions. We used to cache some
+ // data in the layer. But in layers free mode, we don't have layer which
+ // means we need some other place to cached the data between transaction.
+ // We store the data in frame's property.
+ template <class T>
+ already_AddRefed<T> CreateOrRecycleWebRenderUserData(
+ nsDisplayItem* aItem, bool* aOutIsRecycled = nullptr) {
+ MOZ_ASSERT(aItem);
+ nsIFrame* frame = aItem->Frame();
+ if (aOutIsRecycled) {
+ *aOutIsRecycled = true;
+ }
+
+ WebRenderUserDataTable* userDataTable =
+ frame->GetProperty(WebRenderUserDataProperty::Key());
+
+ if (!userDataTable) {
+ userDataTable = new WebRenderUserDataTable();
+ frame->AddProperty(WebRenderUserDataProperty::Key(), userDataTable);
+ }
+
+ RefPtr<WebRenderUserData>& data = userDataTable->LookupOrInsertWith(
+ WebRenderUserDataKey(aItem->GetPerFrameKey(), T::Type()), [&] {
+ auto data = MakeRefPtr<T>(GetRenderRootStateManager(), aItem);
+ mWebRenderUserDatas.Insert(data);
+ if (aOutIsRecycled) {
+ *aOutIsRecycled = false;
+ }
+ return data;
+ });
+
+ MOZ_ASSERT(data);
+ MOZ_ASSERT(data->GetType() == T::Type());
+
+ // Mark the data as being used. We will remove unused user data in the end
+ // of EndTransaction.
+ data->SetUsed(true);
+
+ switch (T::Type()) {
+ case WebRenderUserData::UserDataType::eCanvas:
+ mLastCanvasDatas.Insert(data->AsCanvasData());
+ break;
+ default:
+ break;
+ }
+
+ RefPtr<T> res = static_cast<T*>(data.get());
+ return res.forget();
+ }
+
+ WebRenderLayerManager* mManager;
+
+ private:
+ RenderRootStateManager* GetRenderRootStateManager();
+ void CreateWebRenderCommands(nsDisplayItem* aItem,
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ nsDisplayListBuilder* aDisplayListBuilder);
+
+ bool ComputeInvalidationForDisplayItem(nsDisplayListBuilder* aBuilder,
+ const nsPoint& aShift,
+ nsDisplayItem* aItem);
+ bool ComputeInvalidationForDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsPoint& aShift,
+ nsDisplayList* aList);
+
+ ClipManager mClipManager;
+ HitTestInfoManager mHitTestInfoManager;
+
+ // We use this as a temporary data structure while building the mScrollData
+ // inside a layers-free transaction.
+ std::vector<WebRenderLayerScrollData> mLayerScrollData;
+ // We use this as a temporary data structure to track the current display
+ // item's ASR as we recurse in CreateWebRenderCommandsFromDisplayList. We
+ // need this so that WebRenderLayerScrollData items that deeper in the
+ // tree don't duplicate scroll metadata that their ancestors already have.
+ std::vector<const ActiveScrolledRoot*> mAsrStack;
+ // A similar stack to track the deferred transform that we decided to emit
+ // most recently.
+ std::vector<nsDisplayTransform*> mDeferredTransformStack;
+ const ActiveScrolledRoot* mLastAsr;
+
+ WebRenderUserDataRefTable mWebRenderUserDatas;
+
+ // Store of WebRenderCanvasData objects for use in empty transactions
+ CanvasDataSet mLastCanvasDatas;
+
+ wr::usize mBuilderDumpIndex;
+ wr::usize mDumpIndent;
+
+ public:
+ // Whether consecutive inactive display items should be grouped into one
+ // blob image.
+ bool mDoGrouping;
+
+ // True if the most recently build display list contained an svg that
+ // we did grouping for.
+ bool mContainsSVGGroup;
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif /* GFX_WEBRENDERCOMMANDBUILDER_H */
diff --git a/gfx/layers/wr/WebRenderDrawEventRecorder.cpp b/gfx/layers/wr/WebRenderDrawEventRecorder.cpp
new file mode 100644
index 0000000000..db4345d592
--- /dev/null
+++ b/gfx/layers/wr/WebRenderDrawEventRecorder.cpp
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * 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 "WebRenderDrawEventRecorder.h"
+#include "mozilla/layers/SharedSurfacesChild.h"
+#include "mozilla/layers/SharedSurfacesParent.h"
+
+namespace mozilla {
+using namespace gfx;
+
+namespace layers {
+
+void WebRenderDrawEventRecorder::StoreSourceSurfaceRecording(
+ SourceSurface* aSurface, const char* aReason) {
+ wr::ExternalImageId extId;
+ nsresult rv = layers::SharedSurfacesChild::Share(aSurface, extId);
+ if (NS_FAILED(rv)) {
+ DrawEventRecorderMemory::StoreSourceSurfaceRecording(aSurface, aReason);
+ return;
+ }
+
+ StoreExternalSurfaceRecording(aSurface, wr::AsUint64(extId));
+}
+
+already_AddRefed<SourceSurface> WebRenderTranslator::LookupExternalSurface(
+ uint64_t aKey) {
+ return SharedSurfacesParent::Get(wr::ToExternalImageId(aKey));
+}
+
+} // namespace layers
+} // namespace mozilla
diff --git a/gfx/layers/wr/WebRenderDrawEventRecorder.h b/gfx/layers/wr/WebRenderDrawEventRecorder.h
new file mode 100644
index 0000000000..9d0fbef684
--- /dev/null
+++ b/gfx/layers/wr/WebRenderDrawEventRecorder.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * 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/. */
+
+#ifndef MOZILLA_LAYERS_WEBRENDERDRAWTARGETRECORDER_H
+#define MOZILLA_LAYERS_WEBRENDERDRAWTARGETRECORDER_H
+
+#include "mozilla/gfx/DrawEventRecorder.h"
+#include "mozilla/gfx/InlineTranslator.h"
+#include "mozilla/webrender/webrender_ffi.h"
+
+namespace mozilla {
+namespace layers {
+
+struct BlobFont {
+ wr::FontInstanceKey mFontInstanceKey;
+ gfx::ReferencePtr mScaledFontPtr;
+};
+
+class WebRenderDrawEventRecorder final : public gfx::DrawEventRecorderMemory {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(WebRenderDrawEventRecorder, final)
+
+ explicit WebRenderDrawEventRecorder(
+ const gfx::SerializeResourcesFn& aSerialize)
+ : DrawEventRecorderMemory(aSerialize) {}
+
+ gfx::RecorderType GetRecorderType() const final {
+ return gfx::RecorderType::WEBRENDER;
+ }
+
+ void StoreSourceSurfaceRecording(gfx::SourceSurface* aSurface,
+ const char* aReason) final;
+
+ private:
+ virtual ~WebRenderDrawEventRecorder() = default;
+};
+
+class WebRenderTranslator final : public gfx::InlineTranslator {
+ public:
+ explicit WebRenderTranslator(gfx::DrawTarget* aDT,
+ void* aFontContext = nullptr)
+ : InlineTranslator(aDT, aFontContext) {}
+
+ already_AddRefed<gfx::SourceSurface> LookupExternalSurface(
+ uint64_t aKey) final;
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif
diff --git a/gfx/layers/wr/WebRenderImageHost.cpp b/gfx/layers/wr/WebRenderImageHost.cpp
new file mode 100644
index 0000000000..e3bbd5d50f
--- /dev/null
+++ b/gfx/layers/wr/WebRenderImageHost.cpp
@@ -0,0 +1,385 @@
+/* -*- 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 "WebRenderImageHost.h"
+
+#include <utility>
+
+#include "mozilla/ScopeExit.h"
+#include "mozilla/gfx/gfxVars.h"
+#include "mozilla/layers/AsyncImagePipelineManager.h"
+#include "mozilla/layers/CompositorThread.h"
+#include "mozilla/layers/CompositorVsyncScheduler.h" // for CompositorVsyncScheduler
+#include "mozilla/layers/KnowsCompositor.h"
+#include "mozilla/layers/RemoteTextureHostWrapper.h"
+#include "mozilla/layers/RemoteTextureMap.h"
+#include "mozilla/layers/WebRenderBridgeParent.h"
+#include "mozilla/layers/WebRenderTextureHost.h"
+#include "mozilla/StaticPrefs_webgl.h"
+#include "nsAString.h"
+#include "nsDebug.h" // for NS_WARNING, NS_ASSERTION
+#include "nsPrintfCString.h" // for nsPrintfCString
+#include "nsString.h" // for nsAutoCString
+
+#if XP_WIN
+# include "mozilla/layers/GpuProcessD3D11TextureMap.h"
+# include "mozilla/layers/TextureHostWrapperD3D11.h"
+#endif
+
+namespace mozilla {
+
+using namespace gfx;
+
+namespace layers {
+
+class ISurfaceAllocator;
+
+WebRenderImageHost::WebRenderImageHost(const TextureInfo& aTextureInfo)
+ : CompositableHost(aTextureInfo), mCurrentAsyncImageManager(nullptr) {}
+
+WebRenderImageHost::~WebRenderImageHost() {
+ MOZ_ASSERT(mPendingRemoteTextureWrappers.empty());
+ MOZ_ASSERT(mWrBridges.empty());
+}
+
+void WebRenderImageHost::OnReleased() {
+ if (!mPendingRemoteTextureWrappers.empty()) {
+ mPendingRemoteTextureWrappers.clear();
+ }
+}
+
+void WebRenderImageHost::UseTextureHost(
+ const nsTArray<TimedTexture>& aTextures) {
+ CompositableHost::UseTextureHost(aTextures);
+ MOZ_ASSERT(aTextures.Length() >= 1);
+
+ if (!mPendingRemoteTextureWrappers.empty()) {
+ mPendingRemoteTextureWrappers.clear();
+ }
+
+ if (mCurrentTextureHost &&
+ mCurrentTextureHost->AsRemoteTextureHostWrapper()) {
+ mCurrentTextureHost = nullptr;
+ }
+
+ nsTArray<TimedImage> newImages;
+
+ for (uint32_t i = 0; i < aTextures.Length(); ++i) {
+ const TimedTexture& t = aTextures[i];
+ MOZ_ASSERT(t.mTexture);
+ if (i + 1 < aTextures.Length() && t.mProducerID == mLastProducerID &&
+ t.mFrameID < mLastFrameID) {
+ // Ignore frames before a frame that we already composited. We don't
+ // ever want to display these frames. This could be important if
+ // the frame producer adjusts timestamps (e.g. to track the audio clock)
+ // and the new frame times are earlier.
+ continue;
+ }
+ TimedImage& img = *newImages.AppendElement();
+ img.mTextureHost = t.mTexture;
+ img.mTimeStamp = t.mTimeStamp;
+ img.mPictureRect = t.mPictureRect;
+ img.mFrameID = t.mFrameID;
+ img.mProducerID = t.mProducerID;
+ img.mTextureHost->SetCropRect(img.mPictureRect);
+ }
+
+ SetImages(std::move(newImages));
+
+ if (GetAsyncRef()) {
+ for (const auto& it : mWrBridges) {
+ RefPtr<WebRenderBridgeParent> wrBridge = it.second->WrBridge();
+ if (wrBridge && wrBridge->CompositorScheduler()) {
+ wrBridge->CompositorScheduler()->ScheduleComposition(
+ wr::RenderReasons::ASYNC_IMAGE);
+ }
+ }
+ }
+
+ // Video producers generally send replacement images with the same frameID but
+ // slightly different timestamps in order to sync with the audio clock. This
+ // means that any CompositeUntil() call we made in Composite() may no longer
+ // guarantee that we'll composite until the next frame is ready. Fix that
+ // here.
+ if (mLastFrameID >= 0 && !mWrBridges.empty()) {
+ for (const auto& img : Images()) {
+ bool frameComesAfter =
+ img.mFrameID > mLastFrameID || img.mProducerID != mLastProducerID;
+ if (frameComesAfter && !img.mTimeStamp.IsNull()) {
+ for (const auto& it : mWrBridges) {
+ RefPtr<WebRenderBridgeParent> wrBridge = it.second->WrBridge();
+ if (wrBridge) {
+ wrBridge->AsyncImageManager()->CompositeUntil(
+ img.mTimeStamp + TimeDuration::FromMilliseconds(BIAS_TIME_MS));
+ }
+ }
+ break;
+ }
+ }
+ }
+}
+
+void WebRenderImageHost::PushPendingRemoteTexture(
+ const RemoteTextureId aTextureId, const RemoteTextureOwnerId aOwnerId,
+ const base::ProcessId aForPid, const gfx::IntSize aSize,
+ const TextureFlags aFlags) {
+ // Ensure aOwnerId is the same as RemoteTextureOwnerId of pending
+ // RemoteTextures.
+ if (!mPendingRemoteTextureWrappers.empty()) {
+ auto* wrapper =
+ mPendingRemoteTextureWrappers.front()->AsRemoteTextureHostWrapper();
+ MOZ_ASSERT(wrapper);
+ if (wrapper->mOwnerId != aOwnerId || wrapper->mForPid != aForPid) {
+ // Clear when RemoteTextureOwner is different.
+ mPendingRemoteTextureWrappers.clear();
+ mWaitingReadyCallback = false;
+ mWaitForRemoteTextureOwner = true;
+ }
+ }
+
+ // Check if waiting for remote texture owner is allowed.
+ if (!(aFlags & TextureFlags::WAIT_FOR_REMOTE_TEXTURE_OWNER)) {
+ mWaitForRemoteTextureOwner = false;
+ }
+
+ RefPtr<TextureHost> texture =
+ RemoteTextureMap::Get()->GetOrCreateRemoteTextureHostWrapper(
+ aTextureId, aOwnerId, aForPid, aSize, aFlags);
+ MOZ_ASSERT(texture);
+ mPendingRemoteTextureWrappers.push_back(
+ CompositableTextureHostRef(texture.get()));
+}
+
+void WebRenderImageHost::UseRemoteTexture() {
+ if (mPendingRemoteTextureWrappers.empty()) {
+ return;
+ }
+
+ const bool useReadyCallback = bool(GetAsyncRef());
+ CompositableTextureHostRef texture;
+
+ if (useReadyCallback) {
+ if (mWaitingReadyCallback) {
+ return;
+ }
+ MOZ_ASSERT(!mWaitingReadyCallback);
+
+ auto readyCallback = [self = RefPtr<WebRenderImageHost>(this)](
+ const RemoteTextureInfo aInfo) {
+ RefPtr<nsIRunnable> runnable = NS_NewRunnableFunction(
+ "WebRenderImageHost::UseRemoteTexture",
+ [self = std::move(self), aInfo]() {
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+
+ if (self->mPendingRemoteTextureWrappers.empty()) {
+ return;
+ }
+
+ auto* wrapper = self->mPendingRemoteTextureWrappers.front()
+ ->AsRemoteTextureHostWrapper();
+ MOZ_ASSERT(wrapper);
+ if (wrapper->mOwnerId != aInfo.mOwnerId ||
+ wrapper->mForPid != aInfo.mForPid) {
+ // obsoleted callback
+ return;
+ }
+
+ self->mWaitingReadyCallback = false;
+ self->UseRemoteTexture();
+ });
+
+ CompositorThread()->Dispatch(runnable.forget());
+ };
+
+ // Check which of the pending remote textures is the most recent and ready.
+ while (!mPendingRemoteTextureWrappers.empty()) {
+ auto* wrapper =
+ mPendingRemoteTextureWrappers.front()->AsRemoteTextureHostWrapper();
+ mWaitingReadyCallback = RemoteTextureMap::Get()->GetRemoteTexture(
+ wrapper, readyCallback, mWaitForRemoteTextureOwner);
+ MOZ_ASSERT_IF(mWaitingReadyCallback, !wrapper->IsReadyForRendering());
+ if (!wrapper->IsReadyForRendering()) {
+ break;
+ }
+ texture = mPendingRemoteTextureWrappers.front();
+ mPendingRemoteTextureWrappers.pop_front();
+ }
+ } else {
+ texture = mPendingRemoteTextureWrappers.front();
+ auto* wrapper = texture->AsRemoteTextureHostWrapper();
+ mPendingRemoteTextureWrappers.pop_front();
+ MOZ_ASSERT(mPendingRemoteTextureWrappers.empty());
+
+ std::function<void(const RemoteTextureInfo&)> function;
+ RemoteTextureMap::Get()->GetRemoteTexture(wrapper, std::move(function),
+ mWaitForRemoteTextureOwner);
+ mWaitForRemoteTextureOwner = false;
+ }
+
+ if (!texture ||
+ (GetAsyncRef() &&
+ !texture->AsRemoteTextureHostWrapper()->IsReadyForRendering())) {
+ return;
+ }
+
+ SetCurrentTextureHost(texture);
+
+ if (GetAsyncRef()) {
+ for (const auto& it : mWrBridges) {
+ RefPtr<WebRenderBridgeParent> wrBridge = it.second->WrBridge();
+ if (wrBridge && wrBridge->CompositorScheduler()) {
+ wrBridge->CompositorScheduler()->ScheduleComposition(
+ wr::RenderReasons::ASYNC_IMAGE);
+ }
+ }
+ }
+}
+
+void WebRenderImageHost::CleanupResources() {
+ ClearImages();
+ SetCurrentTextureHost(nullptr);
+}
+
+void WebRenderImageHost::RemoveTextureHost(TextureHost* aTexture) {
+ CompositableHost::RemoveTextureHost(aTexture);
+ RemoveImagesWithTextureHost(aTexture);
+}
+
+TimeStamp WebRenderImageHost::GetCompositionTime() const {
+ TimeStamp time;
+
+ MOZ_ASSERT(mCurrentAsyncImageManager);
+ if (mCurrentAsyncImageManager) {
+ time = mCurrentAsyncImageManager->GetCompositionTime();
+ }
+ return time;
+}
+
+CompositionOpportunityId WebRenderImageHost::GetCompositionOpportunityId()
+ const {
+ CompositionOpportunityId id;
+
+ MOZ_ASSERT(mCurrentAsyncImageManager);
+ if (mCurrentAsyncImageManager) {
+ id = mCurrentAsyncImageManager->GetCompositionOpportunityId();
+ }
+ return id;
+}
+
+void WebRenderImageHost::AppendImageCompositeNotification(
+ const ImageCompositeNotificationInfo& aInfo) const {
+ if (mCurrentAsyncImageManager) {
+ mCurrentAsyncImageManager->AppendImageCompositeNotification(aInfo);
+ }
+}
+
+TextureHost* WebRenderImageHost::GetAsTextureHostForComposite(
+ AsyncImagePipelineManager* aAsyncImageManager) {
+ MOZ_ASSERT(aAsyncImageManager);
+
+ if (mCurrentTextureHost &&
+ mCurrentTextureHost->AsRemoteTextureHostWrapper()) {
+ return mCurrentTextureHost;
+ }
+
+ mCurrentAsyncImageManager = aAsyncImageManager;
+ const auto onExit =
+ mozilla::MakeScopeExit([&]() { mCurrentAsyncImageManager = nullptr; });
+
+ int imageIndex = ChooseImageIndex();
+ if (imageIndex < 0) {
+ SetCurrentTextureHost(nullptr);
+ return nullptr;
+ }
+
+ if (uint32_t(imageIndex) + 1 < ImagesCount()) {
+ mCurrentAsyncImageManager->CompositeUntil(
+ GetImage(imageIndex + 1)->mTimeStamp +
+ TimeDuration::FromMilliseconds(BIAS_TIME_MS));
+ }
+
+ const TimedImage* img = GetImage(imageIndex);
+
+ RefPtr<TextureHost> texture = img->mTextureHost.get();
+#if XP_WIN
+ // Convert YUV BufferTextureHost to TextureHostWrapperD3D11 if possible
+ if (texture->AsBufferTextureHost()) {
+ auto identifier = aAsyncImageManager->GetTextureFactoryIdentifier();
+ const bool convertToNV12 =
+ StaticPrefs::gfx_video_convert_yuv_to_nv12_image_host_win() &&
+ identifier.mSupportsD3D11NV12 &&
+ KnowsCompositor::SupportsD3D11(identifier) &&
+ texture->GetFormat() == gfx::SurfaceFormat::YUV;
+ if (convertToNV12) {
+ if (!mTextureAllocator) {
+ mTextureAllocator = new TextureWrapperD3D11Allocator();
+ }
+ RefPtr<TextureHost> textureWrapper =
+ TextureHostWrapperD3D11::CreateFromBufferTexture(mTextureAllocator,
+ texture);
+ if (textureWrapper) {
+ texture = textureWrapper;
+ }
+ }
+ }
+#endif
+ SetCurrentTextureHost(texture);
+
+ if (mCurrentAsyncImageManager->GetCompositionTime()) {
+ // We are in a composition. Send ImageCompositeNotifications.
+ OnFinishRendering(imageIndex, img, mAsyncRef.mProcessId, mAsyncRef.mHandle);
+ }
+
+ return mCurrentTextureHost;
+}
+
+void WebRenderImageHost::SetCurrentTextureHost(TextureHost* aTexture) {
+ if (aTexture == mCurrentTextureHost.get()) {
+ return;
+ }
+ mCurrentTextureHost = aTexture;
+}
+
+void WebRenderImageHost::Dump(std::stringstream& aStream, const char* aPrefix,
+ bool aDumpHtml) {
+ for (const auto& img : Images()) {
+ aStream << aPrefix;
+ aStream << (aDumpHtml ? "<ul><li>TextureHost: " : "TextureHost: ");
+ DumpTextureHost(aStream, img.mTextureHost);
+ aStream << (aDumpHtml ? " </li></ul> " : " ");
+ }
+}
+
+void WebRenderImageHost::SetWrBridge(const wr::PipelineId& aPipelineId,
+ WebRenderBridgeParent* aWrBridge) {
+ MOZ_ASSERT(aWrBridge);
+ MOZ_ASSERT(!mCurrentAsyncImageManager);
+#ifdef DEBUG
+ const auto it = mWrBridges.find(wr::AsUint64(aPipelineId));
+ MOZ_ASSERT(it == mWrBridges.end());
+#endif
+ RefPtr<WebRenderBridgeParentRef> ref =
+ aWrBridge->GetWebRenderBridgeParentRef();
+ mWrBridges.emplace(wr::AsUint64(aPipelineId), ref);
+}
+
+void WebRenderImageHost::ClearWrBridge(const wr::PipelineId& aPipelineId,
+ WebRenderBridgeParent* aWrBridge) {
+ MOZ_ASSERT(aWrBridge);
+ MOZ_ASSERT(!mCurrentAsyncImageManager);
+
+ const auto it = mWrBridges.find(wr::AsUint64(aPipelineId));
+ MOZ_ASSERT(it != mWrBridges.end());
+ if (it == mWrBridges.end()) {
+ gfxCriticalNote << "WrBridge mismatch happened";
+ return;
+ }
+ mWrBridges.erase(it);
+ SetCurrentTextureHost(nullptr);
+}
+
+} // namespace layers
+} // namespace mozilla
diff --git a/gfx/layers/wr/WebRenderImageHost.h b/gfx/layers/wr/WebRenderImageHost.h
new file mode 100644
index 0000000000..df7921f47f
--- /dev/null
+++ b/gfx/layers/wr/WebRenderImageHost.h
@@ -0,0 +1,92 @@
+/* -*- 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/. */
+
+#ifndef MOZILLA_GFX_WEBRENDERIMAGEHOST_H
+#define MOZILLA_GFX_WEBRENDERIMAGEHOST_H
+
+#include <deque>
+#include <unordered_map>
+
+#include "CompositableHost.h" // for CompositableHost
+#include "mozilla/layers/ImageComposite.h" // for ImageComposite
+#include "mozilla/WeakPtr.h"
+
+namespace mozilla {
+namespace layers {
+
+class AsyncImagePipelineManager;
+class TextureWrapperD3D11Allocator;
+class WebRenderBridgeParent;
+class WebRenderBridgeParentRef;
+
+/**
+ * ImageHost. Works with ImageClientSingle and ImageClientBuffered
+ */
+class WebRenderImageHost : public CompositableHost, public ImageComposite {
+ public:
+ explicit WebRenderImageHost(const TextureInfo& aTextureInfo);
+ virtual ~WebRenderImageHost();
+
+ void UseTextureHost(const nsTArray<TimedTexture>& aTextures) override;
+ void RemoveTextureHost(TextureHost* aTexture) override;
+
+ void Dump(std::stringstream& aStream, const char* aPrefix = "",
+ bool aDumpHtml = false) override;
+
+ void CleanupResources() override;
+
+ void OnReleased() override;
+
+ uint32_t GetDroppedFrames() override { return GetDroppedFramesAndReset(); }
+
+ WebRenderImageHost* AsWebRenderImageHost() override { return this; }
+
+ void PushPendingRemoteTexture(const RemoteTextureId aTextureId,
+ const RemoteTextureOwnerId aOwnerId,
+ const base::ProcessId aForPid,
+ const gfx::IntSize aSize,
+ const TextureFlags aFlags);
+ void UseRemoteTexture();
+
+ TextureHost* GetAsTextureHostForComposite(
+ AsyncImagePipelineManager* aAsyncImageManager);
+
+ void SetWrBridge(const wr::PipelineId& aPipelineId,
+ WebRenderBridgeParent* aWrBridge);
+
+ void ClearWrBridge(const wr::PipelineId& aPipelineId,
+ WebRenderBridgeParent* aWrBridge);
+
+ TextureHost* GetCurrentTextureHost() { return mCurrentTextureHost; }
+
+ protected:
+ // ImageComposite
+ TimeStamp GetCompositionTime() const override;
+ CompositionOpportunityId GetCompositionOpportunityId() const override;
+ void AppendImageCompositeNotification(
+ const ImageCompositeNotificationInfo& aInfo) const override;
+
+ void SetCurrentTextureHost(TextureHost* aTexture);
+
+ std::unordered_map<uint64_t, RefPtr<WebRenderBridgeParentRef>> mWrBridges;
+
+ AsyncImagePipelineManager* mCurrentAsyncImageManager;
+
+ CompositableTextureHostRef mCurrentTextureHost;
+
+ std::deque<CompositableTextureHostRef> mPendingRemoteTextureWrappers;
+ bool mWaitingReadyCallback = false;
+ bool mWaitForRemoteTextureOwner = true;
+
+#if XP_WIN
+ RefPtr<TextureWrapperD3D11Allocator> mTextureAllocator;
+#endif
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif // MOZILLA_GFX_WEBRENDERIMAGEHOST_H
diff --git a/gfx/layers/wr/WebRenderLayerManager.cpp b/gfx/layers/wr/WebRenderLayerManager.cpp
new file mode 100644
index 0000000000..66b98a7db3
--- /dev/null
+++ b/gfx/layers/wr/WebRenderLayerManager.cpp
@@ -0,0 +1,803 @@
+/* -*- 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 "WebRenderLayerManager.h"
+
+#include "GeckoProfiler.h"
+#include "mozilla/StaticPrefs_apz.h"
+#include "mozilla/StaticPrefs_layers.h"
+#include "mozilla/dom/BrowserChild.h"
+#include "mozilla/gfx/DrawEventRecorder.h"
+#include "mozilla/layers/CompositorBridgeChild.h"
+#include "mozilla/layers/StackingContextHelper.h"
+#include "mozilla/layers/TextureClient.h"
+#include "mozilla/layers/TransactionIdAllocator.h"
+#include "mozilla/layers/WebRenderBridgeChild.h"
+#include "mozilla/layers/UpdateImageHelper.h"
+#include "mozilla/PerfStats.h"
+#include "nsDisplayList.h"
+#include "nsLayoutUtils.h"
+#include "WebRenderCanvasRenderer.h"
+#include "LayerUserData.h"
+
+#ifdef XP_WIN
+# include "gfxDWriteFonts.h"
+# include "mozilla/WindowsProcessMitigations.h"
+#endif
+
+namespace mozilla {
+
+using namespace gfx;
+
+namespace layers {
+
+WebRenderLayerManager::WebRenderLayerManager(nsIWidget* aWidget)
+ : mWidget(aWidget),
+ mLatestTransactionId{0},
+ mNeedsComposite(false),
+ mIsFirstPaint(false),
+ mDestroyed(false),
+ mTarget(nullptr),
+ mPaintSequenceNumber(0),
+ mWebRenderCommandBuilder(this) {
+ MOZ_COUNT_CTOR(WebRenderLayerManager);
+ mStateManager.mLayerManager = this;
+
+ if (XRE_IsContentProcess() &&
+ StaticPrefs::gfx_webrender_enable_item_cache_AtStartup()) {
+ static const size_t kInitialCacheSize = 1024;
+ static const size_t kMaximumCacheSize = 10240;
+
+ mDisplayItemCache.SetCapacity(kInitialCacheSize, kMaximumCacheSize);
+ }
+}
+
+KnowsCompositor* WebRenderLayerManager::AsKnowsCompositor() { return mWrChild; }
+
+bool WebRenderLayerManager::Initialize(
+ PCompositorBridgeChild* aCBChild, wr::PipelineId aLayersId,
+ TextureFactoryIdentifier* aTextureFactoryIdentifier, nsCString& aError) {
+ MOZ_ASSERT(mWrChild == nullptr);
+ MOZ_ASSERT(aTextureFactoryIdentifier);
+
+ // When we fail to initialize WebRender, it is useful to know if it has ever
+ // succeeded, or if this is the first attempt.
+ static bool hasInitialized = false;
+
+ WindowKind windowKind;
+ if (mWidget->GetWindowType() != widget::WindowType::Popup) {
+ windowKind = WindowKind::MAIN;
+ } else {
+ windowKind = WindowKind::SECONDARY;
+ }
+
+ LayoutDeviceIntSize size = mWidget->GetClientSize();
+ // Check widget size
+ if (!wr::WindowSizeSanityCheck(size.width, size.height)) {
+ gfxCriticalNoteOnce << "Widget size is not valid " << size
+ << " isParent: " << XRE_IsParentProcess();
+ }
+
+ PWebRenderBridgeChild* bridge =
+ aCBChild->SendPWebRenderBridgeConstructor(aLayersId, size, windowKind);
+ if (!bridge) {
+ // This should only fail if we attempt to access a layer we don't have
+ // permission for, or more likely, the GPU process crashed again during
+ // reinitialization. We can expect to be notified again to reinitialize
+ // (which may or may not be using WebRender).
+ gfxCriticalNote << "Failed to create WebRenderBridgeChild.";
+ aError.Assign(hasInitialized
+ ? "FEATURE_FAILURE_WEBRENDER_INITIALIZE_IPDL_POST"_ns
+ : "FEATURE_FAILURE_WEBRENDER_INITIALIZE_IPDL_FIRST"_ns);
+ return false;
+ }
+
+ mWrChild = static_cast<WebRenderBridgeChild*>(bridge);
+
+ TextureFactoryIdentifier textureFactoryIdentifier;
+ wr::MaybeIdNamespace idNamespace;
+ // Sync ipc
+ if (!WrBridge()->SendEnsureConnected(&textureFactoryIdentifier, &idNamespace,
+ &aError)) {
+ gfxCriticalNote << "Failed as lost WebRenderBridgeChild.";
+ aError.Assign(hasInitialized
+ ? "FEATURE_FAILURE_WEBRENDER_INITIALIZE_SYNC_POST"_ns
+ : "FEATURE_FAILURE_WEBRENDER_INITIALIZE_SYNC_FIRST"_ns);
+ return false;
+ }
+
+ if (textureFactoryIdentifier.mParentBackend == LayersBackend::LAYERS_NONE ||
+ idNamespace.isNothing()) {
+ gfxCriticalNote << "Failed to connect WebRenderBridgeChild. isParent="
+ << XRE_IsParentProcess();
+ aError.Append(hasInitialized ? "_POST"_ns : "_FIRST"_ns);
+ return false;
+ }
+
+ WrBridge()->SetWebRenderLayerManager(this);
+ WrBridge()->IdentifyTextureHost(textureFactoryIdentifier);
+ WrBridge()->SetNamespace(idNamespace.ref());
+ *aTextureFactoryIdentifier = textureFactoryIdentifier;
+
+ mDLBuilder = MakeUnique<wr::DisplayListBuilder>(
+ WrBridge()->GetPipeline(), WrBridge()->GetWebRenderBackend());
+
+ hasInitialized = true;
+ return true;
+}
+
+void WebRenderLayerManager::Destroy() { DoDestroy(/* aIsSync */ false); }
+
+void WebRenderLayerManager::DoDestroy(bool aIsSync) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (IsDestroyed()) {
+ return;
+ }
+
+ mDLBuilder = nullptr;
+ mUserData.Destroy();
+ mPartialPrerenderedAnimations.Clear();
+
+ mStateManager.Destroy();
+
+ if (WrBridge()) {
+ WrBridge()->Destroy(aIsSync);
+ }
+
+ mWebRenderCommandBuilder.Destroy();
+
+ if (mTransactionIdAllocator) {
+ // Make sure to notify the refresh driver just in case it's waiting on a
+ // pending transaction. Do this at the top of the event loop so we don't
+ // cause a paint to occur during compositor shutdown.
+ RefPtr<TransactionIdAllocator> allocator = mTransactionIdAllocator;
+ TransactionId id = mLatestTransactionId;
+
+ RefPtr<Runnable> task = NS_NewRunnableFunction(
+ "TransactionIdAllocator::NotifyTransactionCompleted",
+ [allocator, id]() -> void {
+ allocator->ClearPendingTransactions();
+ allocator->NotifyTransactionCompleted(id);
+ });
+ NS_DispatchToMainThread(task.forget());
+ }
+
+ // Forget the widget pointer in case we outlive our owning widget.
+ mWidget = nullptr;
+ mDestroyed = true;
+}
+
+WebRenderLayerManager::~WebRenderLayerManager() {
+ Destroy();
+ MOZ_COUNT_DTOR(WebRenderLayerManager);
+}
+
+CompositorBridgeChild* WebRenderLayerManager::GetCompositorBridgeChild() {
+ return WrBridge()->GetCompositorBridgeChild();
+}
+
+void WebRenderLayerManager::GetBackendName(nsAString& name) {
+ if (WrBridge()->UsingSoftwareWebRenderD3D11()) {
+ name.AssignLiteral("WebRender (Software D3D11)");
+ } else if (WrBridge()->UsingSoftwareWebRenderOpenGL()) {
+ name.AssignLiteral("WebRender (Software OpenGL)");
+ } else if (WrBridge()->UsingSoftwareWebRender()) {
+ name.AssignLiteral("WebRender (Software)");
+ } else {
+ name.AssignLiteral("WebRender");
+ }
+}
+
+uint32_t WebRenderLayerManager::StartFrameTimeRecording(int32_t aBufferSize) {
+ CompositorBridgeChild* renderer = GetCompositorBridgeChild();
+ if (renderer) {
+ uint32_t startIndex;
+ renderer->SendStartFrameTimeRecording(aBufferSize, &startIndex);
+ return startIndex;
+ }
+ return -1;
+}
+
+void WebRenderLayerManager::StopFrameTimeRecording(
+ uint32_t aStartIndex, nsTArray<float>& aFrameIntervals) {
+ CompositorBridgeChild* renderer = GetCompositorBridgeChild();
+ if (renderer) {
+ renderer->SendStopFrameTimeRecording(aStartIndex, &aFrameIntervals);
+ }
+}
+
+void WebRenderLayerManager::TakeCompositionPayloads(
+ nsTArray<CompositionPayload>& aPayloads) {
+ aPayloads.Clear();
+
+ std::swap(mPayload, aPayloads);
+}
+
+bool WebRenderLayerManager::BeginTransactionWithTarget(gfxContext* aTarget,
+ const nsCString& aURL) {
+ mTarget = aTarget;
+ bool retval = BeginTransaction(aURL);
+ if (!retval) {
+ mTarget = nullptr;
+ }
+ return retval;
+}
+
+bool WebRenderLayerManager::BeginTransaction(const nsCString& aURL) {
+ if (!WrBridge()->IPCOpen()) {
+ gfxCriticalNote << "IPC Channel is already torn down unexpectedly\n";
+ return false;
+ }
+
+ mTransactionStart = TimeStamp::Now();
+ mURL = aURL;
+
+ // Increment the paint sequence number even if test logging isn't
+ // enabled in this process; it may be enabled in the parent process,
+ // and the parent process expects unique sequence numbers.
+ ++mPaintSequenceNumber;
+ if (StaticPrefs::apz_test_logging_enabled()) {
+ mApzTestData.StartNewPaint(mPaintSequenceNumber);
+ }
+ return true;
+}
+
+bool WebRenderLayerManager::EndEmptyTransaction(EndTransactionFlags aFlags) {
+ auto clearTarget = MakeScopeExit([&] { mTarget = nullptr; });
+
+ // If we haven't sent a display list (since creation or since the last time we
+ // sent ClearDisplayList to the parent) then we can't do an empty transaction
+ // because the parent doesn't have a display list for us and we need to send a
+ // display list first.
+ if (!WrBridge()->GetSentDisplayList()) {
+ return false;
+ }
+
+ mDisplayItemCache.SkipWaitingForPartialDisplayList();
+
+ // Don't block on hidden windows on Linux as it may block all rendering.
+ const bool throttle = mWidget->IsMapped();
+ mLatestTransactionId = mTransactionIdAllocator->GetTransactionId(throttle);
+
+ if (aFlags & EndTransactionFlags::END_NO_COMPOSITE &&
+ !mWebRenderCommandBuilder.NeedsEmptyTransaction()) {
+ if (mPendingScrollUpdates.IsEmpty()) {
+ MOZ_ASSERT(!mTarget);
+ WrBridge()->SendSetFocusTarget(mFocusTarget);
+ // Revoke TransactionId to trigger next paint.
+ mTransactionIdAllocator->RevokeTransactionId(mLatestTransactionId);
+ mLatestTransactionId = mLatestTransactionId.Prev();
+ return true;
+ }
+ }
+
+ LayoutDeviceIntSize size = mWidget->GetClientSize();
+ WrBridge()->BeginTransaction();
+
+ mWebRenderCommandBuilder.EmptyTransaction();
+
+ // Get the time of when the refresh driver start its tick (if available),
+ // otherwise use the time of when LayerManager::BeginTransaction was called.
+ TimeStamp refreshStart = mTransactionIdAllocator->GetTransactionStart();
+ if (!refreshStart) {
+ refreshStart = mTransactionStart;
+ }
+
+ // Skip the synchronization for buffer since we also skip the painting during
+ // device-reset status.
+ if (!gfxPlatform::GetPlatform()->DidRenderingDeviceReset()) {
+ if (WrBridge()->GetSyncObject() &&
+ WrBridge()->GetSyncObject()->IsSyncObjectValid()) {
+ WrBridge()->GetSyncObject()->Synchronize();
+ }
+ }
+
+ GetCompositorBridgeChild()->EndCanvasTransaction();
+
+ Maybe<TransactionData> transactionData;
+ if (mStateManager.mAsyncResourceUpdates || !mPendingScrollUpdates.IsEmpty() ||
+ WrBridge()->HasWebRenderParentCommands()) {
+ transactionData.emplace();
+ transactionData->mIdNamespace = WrBridge()->GetNamespace();
+ transactionData->mPaintSequenceNumber = mPaintSequenceNumber;
+ if (mStateManager.mAsyncResourceUpdates) {
+ mStateManager.mAsyncResourceUpdates->Flush(
+ transactionData->mResourceUpdates, transactionData->mSmallShmems,
+ transactionData->mLargeShmems);
+ }
+ transactionData->mScrollUpdates = std::move(mPendingScrollUpdates);
+ for (const auto& scrollId : transactionData->mScrollUpdates.Keys()) {
+ nsLayoutUtils::NotifyPaintSkipTransaction(/*scroll id=*/scrollId);
+ }
+ }
+
+ Maybe<wr::IpcResourceUpdateQueue> nothing;
+ WrBridge()->EndEmptyTransaction(mFocusTarget, std::move(transactionData),
+ mLatestTransactionId,
+ mTransactionIdAllocator->GetVsyncId(),
+ mTransactionIdAllocator->GetVsyncStart(),
+ refreshStart, mTransactionStart, mURL);
+ mTransactionStart = TimeStamp();
+
+ MakeSnapshotIfRequired(size);
+ return true;
+}
+
+void WebRenderLayerManager::EndTransactionWithoutLayer(
+ nsDisplayList* aDisplayList, nsDisplayListBuilder* aDisplayListBuilder,
+ WrFiltersHolder&& aFilters, WebRenderBackgroundData* aBackground,
+ const double aGeckoDLBuildTime) {
+ AUTO_PROFILER_TRACING_MARKER("Paint", "WrDisplayList", GRAPHICS);
+
+ auto clearTarget = MakeScopeExit([&] { mTarget = nullptr; });
+
+ WrBridge()->BeginTransaction();
+
+ LayoutDeviceIntSize size = mWidget->GetClientSize();
+
+ mDLBuilder->Begin(&mDisplayItemCache);
+
+ wr::IpcResourceUpdateQueue resourceUpdates(WrBridge());
+ wr::usize builderDumpIndex = 0;
+ bool containsSVGGroup = false;
+ bool dumpEnabled =
+ mWebRenderCommandBuilder.ShouldDumpDisplayList(aDisplayListBuilder);
+ Maybe<AutoDisplayItemCacheSuppressor> cacheSuppressor;
+ if (dumpEnabled) {
+ cacheSuppressor.emplace(&mDisplayItemCache);
+ printf_stderr("-- WebRender display list build --\n");
+ }
+
+ if (XRE_IsContentProcess() &&
+ StaticPrefs::gfx_webrender_debug_dl_dump_content_serialized()) {
+ mDLBuilder->DumpSerializedDisplayList();
+ }
+
+ if (aDisplayList) {
+ MOZ_ASSERT(aDisplayListBuilder && !aBackground);
+ mDisplayItemCache.SetDisplayList(aDisplayListBuilder, aDisplayList);
+
+ mWebRenderCommandBuilder.BuildWebRenderCommands(
+ *mDLBuilder, resourceUpdates, aDisplayList, aDisplayListBuilder,
+ mScrollData, std::move(aFilters));
+
+ aDisplayListBuilder->NotifyAndClearScrollFrames();
+
+ builderDumpIndex = mWebRenderCommandBuilder.GetBuilderDumpIndex();
+ containsSVGGroup = mWebRenderCommandBuilder.GetContainsSVGGroup();
+ } else {
+ // ViewToPaint does not have frame yet, then render only background clolor.
+ MOZ_ASSERT(!aDisplayListBuilder && aBackground);
+ aBackground->AddWebRenderCommands(*mDLBuilder);
+ if (dumpEnabled) {
+ printf_stderr("(no display list; background only)\n");
+ builderDumpIndex =
+ mDLBuilder->Dump(/*indent*/ 1, Some(builderDumpIndex), Nothing());
+ }
+ }
+
+ if (AsyncPanZoomEnabled()) {
+ if (mIsFirstPaint) {
+ mScrollData.SetIsFirstPaint();
+ mIsFirstPaint = false;
+ }
+ mScrollData.SetPaintSequenceNumber(mPaintSequenceNumber);
+ if (dumpEnabled) {
+ std::stringstream str;
+ str << mScrollData;
+ print_stderr(str);
+ }
+ }
+
+ // Since we're sending a full mScrollData that will include the new scroll
+ // offsets, and we can throw away the pending scroll updates we had kept for
+ // an empty transaction.
+ auto scrollIdsUpdated = ClearPendingScrollInfoUpdate();
+ for (ScrollableLayerGuid::ViewID update : scrollIdsUpdated) {
+ nsLayoutUtils::NotifyPaintSkipTransaction(update);
+ }
+
+ // Don't block on hidden windows on Linux as it may block all rendering.
+ const bool throttle = mWidget->IsMapped();
+ mLatestTransactionId = mTransactionIdAllocator->GetTransactionId(throttle);
+
+ // Get the time of when the refresh driver start its tick (if available),
+ // otherwise use the time of when LayerManager::BeginTransaction was called.
+ TimeStamp refreshStart = mTransactionIdAllocator->GetTransactionStart();
+ if (!refreshStart) {
+ refreshStart = mTransactionStart;
+ }
+
+ if (mStateManager.mAsyncResourceUpdates) {
+ if (resourceUpdates.IsEmpty()) {
+ resourceUpdates.ReplaceResources(
+ std::move(mStateManager.mAsyncResourceUpdates.ref()));
+ } else {
+ WrBridge()->UpdateResources(mStateManager.mAsyncResourceUpdates.ref());
+ }
+ mStateManager.mAsyncResourceUpdates.reset();
+ }
+ mStateManager.DiscardImagesInTransaction(resourceUpdates);
+
+ WrBridge()->RemoveExpiredFontKeys(resourceUpdates);
+
+ // Skip the synchronization for buffer since we also skip the painting during
+ // device-reset status.
+ if (!gfxPlatform::GetPlatform()->DidRenderingDeviceReset()) {
+ if (WrBridge()->GetSyncObject() &&
+ WrBridge()->GetSyncObject()->IsSyncObjectValid()) {
+ WrBridge()->GetSyncObject()->Synchronize();
+ }
+ }
+
+ GetCompositorBridgeChild()->EndCanvasTransaction();
+
+ {
+ AUTO_PROFILER_TRACING_MARKER("Paint", "ForwardDPTransaction", GRAPHICS);
+ DisplayListData dlData;
+ mDLBuilder->End(dlData);
+ resourceUpdates.Flush(dlData.mResourceUpdates, dlData.mSmallShmems,
+ dlData.mLargeShmems);
+ dlData.mRect =
+ LayoutDeviceRect(LayoutDevicePoint(), LayoutDeviceSize(size));
+ dlData.mScrollData.emplace(std::move(mScrollData));
+ dlData.mDLDesc.gecko_display_list_type =
+ aDisplayListBuilder && aDisplayListBuilder->PartialBuildFailed()
+ ? wr::GeckoDisplayListType::Full(aGeckoDLBuildTime)
+ : wr::GeckoDisplayListType::Partial(aGeckoDLBuildTime);
+
+ // convert from nanoseconds to microseconds
+ auto duration = TimeDuration::FromMicroseconds(
+ double(dlData.mDLDesc.builder_finish_time -
+ dlData.mDLDesc.builder_start_time) /
+ 1000.);
+ PerfStats::RecordMeasurement(PerfStats::Metric::WrDisplayListBuilding,
+ duration);
+ bool ret = WrBridge()->EndTransaction(
+ std::move(dlData), mLatestTransactionId, containsSVGGroup,
+ mTransactionIdAllocator->GetVsyncId(),
+ mTransactionIdAllocator->GetVsyncStart(), refreshStart,
+ mTransactionStart, mURL);
+ if (!ret) {
+ // Failed to send display list, reset display item cache state.
+ mDisplayItemCache.Clear();
+ }
+
+ WrBridge()->SendSetFocusTarget(mFocusTarget);
+ mFocusTarget = FocusTarget();
+ }
+
+ // Discard animations after calling WrBridge()->EndTransaction().
+ // It updates mWrEpoch in WebRenderBridgeParent. The updated mWrEpoch is
+ // necessary for deleting animations at the correct time.
+ mStateManager.DiscardCompositorAnimations();
+
+ mTransactionStart = TimeStamp();
+
+ MakeSnapshotIfRequired(size);
+ mNeedsComposite = false;
+}
+
+void WebRenderLayerManager::SetFocusTarget(const FocusTarget& aFocusTarget) {
+ mFocusTarget = aFocusTarget;
+}
+
+bool WebRenderLayerManager::AsyncPanZoomEnabled() const {
+ return mWidget->AsyncPanZoomEnabled();
+}
+
+IntRect ToOutsideIntRect(const gfxRect& aRect) {
+ return IntRect::RoundOut(aRect.X(), aRect.Y(), aRect.Width(), aRect.Height());
+}
+
+void WebRenderLayerManager::MakeSnapshotIfRequired(LayoutDeviceIntSize aSize) {
+ auto clearTarget = MakeScopeExit([&] { mTarget = nullptr; });
+
+ if (!mTarget || !mTarget->GetDrawTarget() || aSize.IsEmpty()) {
+ return;
+ }
+
+ // XXX Add other TextureData supports.
+ // Only BufferTexture is supported now.
+
+ // TODO: fixup for proper surface format.
+ // The GLES spec only guarantees that RGBA can be used with glReadPixels,
+ // so on Android we use RGBA.
+ SurfaceFormat format =
+#ifdef MOZ_WIDGET_ANDROID
+ SurfaceFormat::R8G8B8A8;
+#else
+ SurfaceFormat::B8G8R8A8;
+#endif
+ RefPtr<TextureClient> texture = TextureClient::CreateForRawBufferAccess(
+ WrBridge(), format, aSize.ToUnknownSize(), BackendType::SKIA,
+ TextureFlags::SNAPSHOT);
+ if (!texture) {
+ return;
+ }
+
+ // The other side knows our ContentParentId and WebRenderBridgeChild will
+ // ignore the one provided here in favour of what WebRenderBridgeParent
+ // already has.
+ texture->InitIPDLActor(WrBridge(), dom::ContentParentId());
+ if (!texture->GetIPDLActor()) {
+ return;
+ }
+
+ IntRect bounds = ToOutsideIntRect(mTarget->GetClipExtents());
+ bool needsYFlip = false;
+ if (!WrBridge()->SendGetSnapshot(WrapNotNull(texture->GetIPDLActor()),
+ &needsYFlip)) {
+ return;
+ }
+
+ TextureClientAutoLock autoLock(texture, OpenMode::OPEN_READ_ONLY);
+ if (!autoLock.Succeeded()) {
+ return;
+ }
+ RefPtr<DrawTarget> drawTarget = texture->BorrowDrawTarget();
+ if (!drawTarget || !drawTarget->IsValid()) {
+ return;
+ }
+ RefPtr<SourceSurface> snapshot = drawTarget->Snapshot();
+ /*
+ static int count = 0;
+ char filename[100];
+ snprintf(filename, 100, "output%d.png", count++);
+ printf_stderr("Writing to :%s\n", filename);
+ gfxUtils::WriteAsPNG(snapshot, filename);
+ */
+
+ Rect dst(bounds.X(), bounds.Y(), bounds.Width(), bounds.Height());
+ Rect src(0, 0, bounds.Width(), bounds.Height());
+
+ Matrix m;
+ if (needsYFlip) {
+ m = Matrix::Scaling(1.0, -1.0).PostTranslate(0.0, aSize.height);
+ }
+ SurfacePattern pattern(snapshot, ExtendMode::CLAMP, m);
+ DrawTarget* dt = mTarget->GetDrawTarget();
+ MOZ_RELEASE_ASSERT(dt);
+ dt->FillRect(dst, pattern);
+
+ mTarget = nullptr;
+}
+
+void WebRenderLayerManager::DiscardImages() {
+ wr::IpcResourceUpdateQueue resources(WrBridge());
+ mStateManager.DiscardImagesInTransaction(resources);
+ WrBridge()->UpdateResources(resources);
+}
+
+void WebRenderLayerManager::DiscardLocalImages() {
+ mStateManager.DiscardLocalImages();
+}
+
+void WebRenderLayerManager::DidComposite(
+ TransactionId aTransactionId, const mozilla::TimeStamp& aCompositeStart,
+ const mozilla::TimeStamp& aCompositeEnd) {
+ if (IsDestroyed()) {
+ return;
+ }
+
+ MOZ_ASSERT(mWidget);
+
+ // Notifying the observers may tick the refresh driver which can cause
+ // a lot of different things to happen that may affect the lifetime of
+ // this layer manager. So let's make sure this object stays alive until
+ // the end of the method invocation.
+ RefPtr<WebRenderLayerManager> selfRef = this;
+
+ // |aTransactionId| will be > 0 if the compositor is acknowledging a shadow
+ // layers transaction.
+ if (aTransactionId.IsValid()) {
+ nsIWidgetListener* listener = mWidget->GetWidgetListener();
+ if (listener) {
+ listener->DidCompositeWindow(aTransactionId, aCompositeStart,
+ aCompositeEnd);
+ }
+ listener = mWidget->GetAttachedWidgetListener();
+ if (listener) {
+ listener->DidCompositeWindow(aTransactionId, aCompositeStart,
+ aCompositeEnd);
+ }
+ if (mTransactionIdAllocator) {
+ mTransactionIdAllocator->NotifyTransactionCompleted(aTransactionId);
+ }
+ }
+}
+
+void WebRenderLayerManager::ClearCachedResources() {
+ if (!WrBridge()->IPCOpen()) {
+ gfxCriticalNote << "IPC Channel is already torn down unexpectedly\n";
+ return;
+ }
+ WrBridge()->BeginClearCachedResources();
+ // We flush any pending async resource updates before we clear the display
+ // list items because some resources (e.g. images) might be shared between
+ // multiple layer managers, not get freed here, and we want to keep their
+ // states consistent.
+ mStateManager.FlushAsyncResourceUpdates();
+ mWebRenderCommandBuilder.ClearCachedResources();
+ DiscardImages();
+ mStateManager.ClearCachedResources();
+ CompositorBridgeChild* compositorBridge = GetCompositorBridgeChild();
+ if (compositorBridge) {
+ compositorBridge->ClearCachedResources();
+ }
+ WrBridge()->EndClearCachedResources();
+}
+
+void WebRenderLayerManager::ClearAnimationResources() {
+ if (!WrBridge()->IPCOpen()) {
+ gfxCriticalNote << "IPC Channel is already torn down unexpectedly\n";
+ return;
+ }
+ WrBridge()->SendClearAnimationResources();
+}
+
+void WebRenderLayerManager::WrUpdated() {
+ ClearAsyncAnimations();
+ mStateManager.mAsyncResourceUpdates.reset();
+ mWebRenderCommandBuilder.ClearCachedResources();
+ DiscardLocalImages();
+ mDisplayItemCache.Clear();
+
+ if (mWidget) {
+ if (dom::BrowserChild* browserChild = mWidget->GetOwningBrowserChild()) {
+ browserChild->SchedulePaint();
+ }
+ }
+}
+
+void WebRenderLayerManager::UpdateTextureFactoryIdentifier(
+ const TextureFactoryIdentifier& aNewIdentifier) {
+ WrBridge()->IdentifyTextureHost(aNewIdentifier);
+}
+
+TextureFactoryIdentifier WebRenderLayerManager::GetTextureFactoryIdentifier() {
+ return WrBridge()->GetTextureFactoryIdentifier();
+}
+
+void WebRenderLayerManager::SetTransactionIdAllocator(
+ TransactionIdAllocator* aAllocator) {
+ // When changing the refresh driver, the previous refresh driver may never
+ // receive updates of pending transactions it's waiting for. So clear the
+ // waiting state before assigning another refresh driver.
+ if (mTransactionIdAllocator && (aAllocator != mTransactionIdAllocator)) {
+ mTransactionIdAllocator->ClearPendingTransactions();
+
+ // We should also reset the transaction id of the new allocator to previous
+ // allocator's last transaction id, so that completed transactions for
+ // previous allocator will be ignored and won't confuse the new allocator.
+ if (aAllocator) {
+ aAllocator->ResetInitialTransactionId(
+ mTransactionIdAllocator->LastTransactionId());
+ }
+ }
+
+ mTransactionIdAllocator = aAllocator;
+}
+
+TransactionId WebRenderLayerManager::GetLastTransactionId() {
+ return mLatestTransactionId;
+}
+
+void WebRenderLayerManager::FlushRendering(wr::RenderReasons aReasons) {
+ CompositorBridgeChild* cBridge = GetCompositorBridgeChild();
+ if (!cBridge) {
+ return;
+ }
+ MOZ_ASSERT(mWidget);
+
+ // If value of IsResizingNativeWidget() is nothing, we assume that resizing
+ // might happen.
+ bool resizing = mWidget && mWidget->IsResizingNativeWidget().valueOr(true);
+
+ if (resizing) {
+ aReasons = aReasons | wr::RenderReasons::RESIZE;
+ }
+
+ // Limit async FlushRendering to !resizing and Win DComp.
+ // XXX relax the limitation
+ if (WrBridge()->GetCompositorUseDComp() && !resizing) {
+ cBridge->SendFlushRenderingAsync(aReasons);
+ } else if (mWidget->SynchronouslyRepaintOnResize() ||
+ StaticPrefs::layers_force_synchronous_resize()) {
+ cBridge->SendFlushRendering(aReasons);
+ } else {
+ cBridge->SendFlushRenderingAsync(aReasons);
+ }
+}
+
+void WebRenderLayerManager::WaitOnTransactionProcessed() {
+ CompositorBridgeChild* bridge = GetCompositorBridgeChild();
+ if (bridge) {
+ bridge->SendWaitOnTransactionProcessed();
+ }
+}
+
+void WebRenderLayerManager::SendInvalidRegion(const nsIntRegion& aRegion) {
+ // XXX Webrender does not support invalid region yet.
+
+#ifndef XP_WIN
+ if (WrBridge()) {
+ WrBridge()->SendInvalidateRenderedFrame();
+ }
+#endif
+}
+
+void WebRenderLayerManager::ScheduleComposite(wr::RenderReasons aReasons) {
+ WrBridge()->SendScheduleComposite(aReasons);
+}
+
+already_AddRefed<PersistentBufferProvider>
+WebRenderLayerManager::CreatePersistentBufferProvider(
+ const gfx::IntSize& aSize, gfx::SurfaceFormat aFormat,
+ bool aWillReadFrequently) {
+ // Only initialize devices if hardware acceleration may possibly be used.
+ // Remoting moves hardware usage out-of-process, while will-read-frequently
+ // avoids hardware acceleration entirely.
+ if (!aWillReadFrequently && !gfxPlatform::UseRemoteCanvas()) {
+#ifdef XP_WIN
+ // Any kind of hardware acceleration is incompatible with Win32k Lockdown
+ // We don't initialize devices here so that PersistentBufferProviderShared
+ // will fall back to using a piece of shared memory as a backing for the
+ // canvas
+ if (!IsWin32kLockedDown()) {
+ gfxPlatform::GetPlatform()->EnsureDevicesInitialized();
+ }
+#else
+ gfxPlatform::GetPlatform()->EnsureDevicesInitialized();
+#endif
+ }
+
+ RefPtr<PersistentBufferProvider> provider =
+ PersistentBufferProviderShared::Create(
+ aSize, aFormat, AsKnowsCompositor(), aWillReadFrequently);
+ if (provider) {
+ return provider.forget();
+ }
+
+ return WindowRenderer::CreatePersistentBufferProvider(aSize, aFormat);
+}
+
+void WebRenderLayerManager::ClearAsyncAnimations() {
+ mStateManager.ClearAsyncAnimations();
+}
+
+void WebRenderLayerManager::WrReleasedImages(
+ const nsTArray<wr::ExternalImageKeyPair>& aPairs) {
+ mStateManager.WrReleasedImages(aPairs);
+}
+
+void WebRenderLayerManager::GetFrameUniformity(FrameUniformityData* aOutData) {
+ WrBridge()->SendGetFrameUniformity(aOutData);
+}
+
+/*static*/
+void WebRenderLayerManager::LayerUserDataDestroy(void* data) {
+ delete static_cast<LayerUserData*>(data);
+}
+
+UniquePtr<LayerUserData> WebRenderLayerManager::RemoveUserData(void* aKey) {
+ UniquePtr<LayerUserData> d(static_cast<LayerUserData*>(
+ mUserData.Remove(static_cast<gfx::UserDataKey*>(aKey))));
+ return d;
+}
+
+std::unordered_set<ScrollableLayerGuid::ViewID>
+WebRenderLayerManager::ClearPendingScrollInfoUpdate() {
+ std::unordered_set<ScrollableLayerGuid::ViewID> scrollIds(
+ mPendingScrollUpdates.Keys().cbegin(),
+ mPendingScrollUpdates.Keys().cend());
+ mPendingScrollUpdates.Clear();
+ return scrollIds;
+}
+
+} // namespace layers
+} // namespace mozilla
diff --git a/gfx/layers/wr/WebRenderLayerManager.h b/gfx/layers/wr/WebRenderLayerManager.h
new file mode 100644
index 0000000000..31fc9b6678
--- /dev/null
+++ b/gfx/layers/wr/WebRenderLayerManager.h
@@ -0,0 +1,281 @@
+/* -*- 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/. */
+
+#ifndef GFX_WEBRENDERLAYERMANAGER_H
+#define GFX_WEBRENDERLAYERMANAGER_H
+
+#include <cstddef> // for size_t
+#include <cstdint> // for uint32_t, int32_t, INT32_MAX
+#include <string> // for string
+#include "Units.h" // for LayoutDeviceIntSize
+#include "mozilla/AlreadyAddRefed.h" // for already_AddRefed
+#include "mozilla/Assertions.h" // for AssertionConditionType, MOZ_ASSERT, MOZ_ASSERT_HELPER2
+#include "mozilla/Attributes.h" // for MOZ_NON_OWNING_REF
+#include "mozilla/RefPtr.h" // for RefPtr
+#include "mozilla/StaticPrefs_apz.h" // for apz_test_logging_enabled
+#include "mozilla/TimeStamp.h" // for TimeStamp
+#include "mozilla/gfx/Point.h" // for IntSize
+#include "mozilla/gfx/Types.h" // for SurfaceFormat
+#include "mozilla/layers/APZTestData.h" // for APZTestData
+#include "mozilla/layers/CompositorTypes.h" // for TextureFactoryIdentifier
+#include "mozilla/layers/DisplayItemCache.h" // for DisplayItemCache
+#include "mozilla/layers/FocusTarget.h" // for FocusTarget
+#include "mozilla/layers/LayersTypes.h" // for TransactionId, LayersBackend, CompositionPayload (ptr only), LayersBackend::...
+#include "mozilla/layers/RenderRootStateManager.h" // for RenderRootStateManager
+#include "mozilla/layers/ScrollableLayerGuid.h" // for ScrollableLayerGuid, ScrollableLayerGuid::ViewID
+#include "mozilla/layers/WebRenderCommandBuilder.h" // for WebRenderCommandBuilder
+#include "mozilla/layers/WebRenderScrollData.h" // for WebRenderScrollData
+#include "WindowRenderer.h"
+#include "nsHashKeys.h" // for nsRefPtrHashKey
+#include "nsRegion.h" // for nsIntRegion
+#include "nsStringFwd.h" // for nsCString, nsAString
+#include "nsTArray.h" // for nsTArray
+#include "nsTHashSet.h"
+
+class gfxContext;
+class nsIWidget;
+
+namespace mozilla {
+
+class nsDisplayList;
+class nsDisplayListBuilder;
+struct ActiveScrolledRoot;
+
+namespace layers {
+
+class CompositorBridgeChild;
+class KnowsCompositor;
+class Layer;
+class PCompositorBridgeChild;
+class WebRenderBridgeChild;
+class WebRenderParentCommand;
+class TransactionIdAllocator;
+class LayerUserData;
+
+class WebRenderLayerManager final : public WindowRenderer {
+ typedef nsTHashSet<RefPtr<WebRenderUserData>> WebRenderUserDataRefTable;
+
+ public:
+ explicit WebRenderLayerManager(nsIWidget* aWidget);
+ bool Initialize(PCompositorBridgeChild* aCBChild, wr::PipelineId aLayersId,
+ TextureFactoryIdentifier* aTextureFactoryIdentifier,
+ nsCString& aError);
+
+ void Destroy() override;
+ bool IsDestroyed() { return mDestroyed; }
+
+ void DoDestroy(bool aIsSync);
+
+ protected:
+ virtual ~WebRenderLayerManager();
+
+ public:
+ KnowsCompositor* AsKnowsCompositor() override;
+ WebRenderLayerManager* AsWebRender() override { return this; }
+ CompositorBridgeChild* GetCompositorBridgeChild() override;
+
+ // WebRender can handle images larger than the max texture size via tiling.
+ int32_t GetMaxTextureSize() const override { return INT32_MAX; }
+
+ bool BeginTransactionWithTarget(gfxContext* aTarget, const nsCString& aURL);
+ bool BeginTransaction(const nsCString& aURL) override;
+ bool EndEmptyTransaction(EndTransactionFlags aFlags = END_DEFAULT) override;
+ void EndTransactionWithoutLayer(nsDisplayList* aDisplayList,
+ nsDisplayListBuilder* aDisplayListBuilder,
+ WrFiltersHolder&& aFilters,
+ WebRenderBackgroundData* aBackground,
+ const double aGeckoDLBuildTime);
+
+ LayersBackend GetBackendType() override { return LayersBackend::LAYERS_WR; }
+ void GetBackendName(nsAString& name) override;
+
+ bool NeedsWidgetInvalidation() override { return false; }
+
+ void DidComposite(TransactionId aTransactionId,
+ const mozilla::TimeStamp& aCompositeStart,
+ const mozilla::TimeStamp& aCompositeEnd);
+
+ void ClearCachedResources();
+ void ClearAnimationResources();
+ void UpdateTextureFactoryIdentifier(
+ const TextureFactoryIdentifier& aNewIdentifier);
+ TextureFactoryIdentifier GetTextureFactoryIdentifier();
+
+ void SetTransactionIdAllocator(TransactionIdAllocator* aAllocator);
+ TransactionId GetLastTransactionId();
+
+ void FlushRendering(wr::RenderReasons aReasons) override;
+ void WaitOnTransactionProcessed() override;
+
+ void SendInvalidRegion(const nsIntRegion& aRegion);
+
+ void ScheduleComposite(wr::RenderReasons aReasons);
+
+ void SetNeedsComposite(bool aNeedsComposite) {
+ mNeedsComposite = aNeedsComposite;
+ }
+ bool NeedsComposite() const { return mNeedsComposite; }
+ void SetIsFirstPaint() { mIsFirstPaint = true; }
+ bool GetIsFirstPaint() const { return mIsFirstPaint; }
+ void SetFocusTarget(const FocusTarget& aFocusTarget);
+
+ already_AddRefed<PersistentBufferProvider> CreatePersistentBufferProvider(
+ const gfx::IntSize& aSize, gfx::SurfaceFormat aFormat,
+ bool aWillReadFrequently) override;
+
+ bool AsyncPanZoomEnabled() const;
+
+ // adds an imagekey to a list of keys that will be discarded on the next
+ // transaction or destruction
+ void DiscardImages();
+ void DiscardLocalImages();
+
+ void ClearAsyncAnimations();
+ void WrReleasedImages(const nsTArray<wr::ExternalImageKeyPair>& aPairs);
+
+ WebRenderBridgeChild* WrBridge() const { return mWrChild; }
+
+ // See equivalent function in ClientLayerManager
+ void LogTestDataForCurrentPaint(ScrollableLayerGuid::ViewID aScrollId,
+ const std::string& aKey,
+ const std::string& aValue) {
+ MOZ_ASSERT(StaticPrefs::apz_test_logging_enabled(), "don't call me");
+ mApzTestData.LogTestDataForPaint(mPaintSequenceNumber, aScrollId, aKey,
+ aValue);
+ }
+ void LogAdditionalTestData(const std::string& aKey,
+ const std::string& aValue) {
+ MOZ_ASSERT(StaticPrefs::apz_test_logging_enabled(), "don't call me");
+ mApzTestData.RecordAdditionalData(aKey, aValue);
+ }
+
+ // See equivalent function in ClientLayerManager
+ const APZTestData& GetAPZTestData() const { return mApzTestData; }
+
+ WebRenderCommandBuilder& CommandBuilder() { return mWebRenderCommandBuilder; }
+ WebRenderUserDataRefTable* GetWebRenderUserDataTable() {
+ return mWebRenderCommandBuilder.GetWebRenderUserDataTable();
+ }
+ WebRenderScrollData& GetScrollData() { return mScrollData; }
+
+ void WrUpdated();
+ nsIWidget* GetWidget() { return mWidget; }
+
+ uint32_t StartFrameTimeRecording(int32_t aBufferSize) override;
+ void StopFrameTimeRecording(uint32_t aStartIndex,
+ nsTArray<float>& aFrameIntervals) override;
+
+ RenderRootStateManager* GetRenderRootStateManager() { return &mStateManager; }
+
+ void TakeCompositionPayloads(nsTArray<CompositionPayload>& aPayloads);
+
+ void GetFrameUniformity(FrameUniformityData* aOutData) override;
+
+ void RegisterPayloads(const nsTArray<CompositionPayload>& aPayload) {
+ mPayload.AppendElements(aPayload);
+ MOZ_ASSERT(mPayload.Length() < 10000);
+ }
+
+ static void LayerUserDataDestroy(void* data);
+ /**
+ * This setter can be used anytime. The user data for all keys is
+ * initially null. Ownership pases to the layer manager.
+ */
+ void SetUserData(void* aKey, LayerUserData* aData) {
+ mUserData.Add(static_cast<gfx::UserDataKey*>(aKey), aData,
+ LayerUserDataDestroy);
+ }
+ /**
+ * This can be used anytime. Ownership passes to the caller!
+ */
+ UniquePtr<LayerUserData> RemoveUserData(void* aKey);
+
+ /**
+ * This getter can be used anytime.
+ */
+ bool HasUserData(void* aKey) {
+ return mUserData.Has(static_cast<gfx::UserDataKey*>(aKey));
+ }
+ /**
+ * This getter can be used anytime. Ownership is retained by the layer
+ * manager.
+ */
+ LayerUserData* GetUserData(void* aKey) const {
+ return static_cast<LayerUserData*>(
+ mUserData.Get(static_cast<gfx::UserDataKey*>(aKey)));
+ }
+
+ std::unordered_set<ScrollableLayerGuid::ViewID>
+ ClearPendingScrollInfoUpdate();
+
+#ifdef DEBUG
+ gfxContext* GetTarget() const { return mTarget; }
+#endif
+
+ private:
+ /**
+ * Take a snapshot of the parent context, and copy
+ * it into mTarget.
+ */
+ void MakeSnapshotIfRequired(LayoutDeviceIntSize aSize);
+
+ private:
+ nsIWidget* MOZ_NON_OWNING_REF mWidget;
+
+ RefPtr<WebRenderBridgeChild> mWrChild;
+
+ RefPtr<TransactionIdAllocator> mTransactionIdAllocator;
+ TransactionId mLatestTransactionId;
+
+ gfx::UserData mUserData;
+
+ // This holds the scroll data that we need to send to the compositor for
+ // APZ to do it's job
+ WebRenderScrollData mScrollData;
+
+ bool mNeedsComposite;
+ bool mIsFirstPaint;
+ bool mDestroyed;
+ FocusTarget mFocusTarget;
+
+ // The payload associated with currently pending painting work, for
+ // client layer managers that typically means payload that is part of the
+ // 'upcoming transaction', for HostLayerManagers this typically means
+ // what has been included in received transactions to be presented on the
+ // next composite.
+ // IMPORTANT: Clients should take care to clear this or risk it slowly
+ // growing out of control.
+ nsTArray<CompositionPayload> mPayload;
+
+ // When we're doing a transaction in order to draw to a non-default
+ // target, the layers transaction is only performed in order to send
+ // a PLayers:Update. We save the original non-default target to
+ // mTarget, and then perform the transaction. After the transaction ends,
+ // we send a message to our remote side to capture the actual pixels
+ // being drawn to the default target, and then copy those pixels
+ // back to mTarget.
+ gfxContext* mTarget;
+
+ // See equivalent field in ClientLayerManager
+ uint32_t mPaintSequenceNumber;
+ // See equivalent field in ClientLayerManager
+ APZTestData mApzTestData;
+
+ TimeStamp mTransactionStart;
+ nsCString mURL;
+ WebRenderCommandBuilder mWebRenderCommandBuilder;
+
+ RenderRootStateManager mStateManager;
+ DisplayItemCache mDisplayItemCache;
+ UniquePtr<wr::DisplayListBuilder> mDLBuilder;
+
+ ScrollUpdatesMap mPendingScrollUpdates;
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif /* GFX_WEBRENDERLAYERMANAGER_H */
diff --git a/gfx/layers/wr/WebRenderMessageUtils.h b/gfx/layers/wr/WebRenderMessageUtils.h
new file mode 100644
index 0000000000..9e3862c719
--- /dev/null
+++ b/gfx/layers/wr/WebRenderMessageUtils.h
@@ -0,0 +1,230 @@
+/* -*- 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/. */
+
+#ifndef GFX_WEBRENDERMESSAGEUTILS_H
+#define GFX_WEBRENDERMESSAGEUTILS_H
+
+#include "chrome/common/ipc_message_utils.h"
+
+#include "ipc/EnumSerializer.h"
+#include "ipc/IPCMessageUtils.h"
+#include "mozilla/webrender/webrender_ffi.h"
+#include "mozilla/webrender/WebRenderTypes.h"
+#include "mozilla/dom/MediaIPCUtils.h"
+
+namespace IPC {
+
+template <>
+struct ParamTraits<mozilla::wr::ByteBuffer> {
+ typedef mozilla::wr::ByteBuffer paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.mLength);
+ aWriter->WriteBytes(aParam.mData, aParam.mLength);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ size_t length;
+ return ReadParam(aReader, &length) && aResult->Allocate(length) &&
+ aReader->ReadBytesInto(aResult->mData, length);
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::wr::ImageDescriptor> {
+ typedef mozilla::wr::ImageDescriptor paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.format);
+ WriteParam(aWriter, aParam.width);
+ WriteParam(aWriter, aParam.height);
+ WriteParam(aWriter, aParam.stride);
+ WriteParam(aWriter, aParam.opacity);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ return ReadParam(aReader, &aResult->format) &&
+ ReadParam(aReader, &aResult->width) &&
+ ReadParam(aReader, &aResult->height) &&
+ ReadParam(aReader, &aResult->stride) &&
+ ReadParam(aReader, &aResult->opacity);
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::wr::GeckoDisplayListType::Tag>
+ : public ContiguousEnumSerializer<
+ mozilla::wr::GeckoDisplayListType::Tag,
+ mozilla::wr::GeckoDisplayListType::Tag::None,
+ mozilla::wr::GeckoDisplayListType::Tag::Sentinel> {};
+
+template <>
+struct ParamTraits<mozilla::wr::GeckoDisplayListType> {
+ typedef mozilla::wr::GeckoDisplayListType paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.tag);
+ switch (aParam.tag) {
+ case mozilla::wr::GeckoDisplayListType::Tag::None:
+ break;
+ case mozilla::wr::GeckoDisplayListType::Tag::Partial:
+ WriteParam(aWriter, aParam.partial._0);
+ break;
+ case mozilla::wr::GeckoDisplayListType::Tag::Full:
+ WriteParam(aWriter, aParam.full._0);
+ break;
+ default:
+ MOZ_RELEASE_ASSERT(false, "bad enum variant");
+ }
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ if (!ReadParam(aReader, &aResult->tag)) {
+ return false;
+ }
+ switch (aResult->tag) {
+ case mozilla::wr::GeckoDisplayListType::Tag::None:
+ return true;
+ case mozilla::wr::GeckoDisplayListType::Tag::Partial:
+ return ReadParam(aReader, &aResult->partial._0);
+ case mozilla::wr::GeckoDisplayListType::Tag::Full:
+ return ReadParam(aReader, &aResult->full._0);
+ default:
+ MOZ_RELEASE_ASSERT(false, "bad enum variant");
+ }
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::wr::BuiltDisplayListDescriptor> {
+ typedef mozilla::wr::BuiltDisplayListDescriptor paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.gecko_display_list_type);
+ WriteParam(aWriter, aParam.builder_start_time);
+ WriteParam(aWriter, aParam.builder_finish_time);
+ WriteParam(aWriter, aParam.send_start_time);
+ WriteParam(aWriter, aParam.total_clip_nodes);
+ WriteParam(aWriter, aParam.total_spatial_nodes);
+ WriteParam(aWriter, aParam.cache_size);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ return ReadParam(aReader, &aResult->gecko_display_list_type) &&
+ ReadParam(aReader, &aResult->builder_start_time) &&
+ ReadParam(aReader, &aResult->builder_finish_time) &&
+ ReadParam(aReader, &aResult->send_start_time) &&
+ ReadParam(aReader, &aResult->total_clip_nodes) &&
+ ReadParam(aReader, &aResult->total_spatial_nodes) &&
+ ReadParam(aReader, &aResult->cache_size);
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::wr::IdNamespace>
+ : public PlainOldDataSerializer<mozilla::wr::IdNamespace> {};
+
+template <>
+struct ParamTraits<mozilla::wr::ImageKey>
+ : public PlainOldDataSerializer<mozilla::wr::ImageKey> {};
+
+template <>
+struct ParamTraits<mozilla::wr::BlobImageKey>
+ : public PlainOldDataSerializer<mozilla::wr::BlobImageKey> {};
+
+template <>
+struct ParamTraits<mozilla::wr::FontKey>
+ : public PlainOldDataSerializer<mozilla::wr::FontKey> {};
+
+template <>
+struct ParamTraits<mozilla::wr::FontInstanceKey>
+ : public PlainOldDataSerializer<mozilla::wr::FontInstanceKey> {};
+
+template <>
+struct ParamTraits<mozilla::wr::FontInstanceOptions>
+ : public PlainOldDataSerializer<mozilla::wr::FontInstanceOptions> {};
+
+template <>
+struct ParamTraits<mozilla::wr::FontInstancePlatformOptions>
+ : public PlainOldDataSerializer<mozilla::wr::FontInstancePlatformOptions> {
+};
+
+template <>
+struct ParamTraits<mozilla::wr::ExternalImageId>
+ : public PlainOldDataSerializer<mozilla::wr::ExternalImageId> {};
+
+template <>
+struct ParamTraits<mozilla::wr::PipelineId>
+ : public PlainOldDataSerializer<mozilla::wr::PipelineId> {};
+
+template <>
+struct ParamTraits<mozilla::wr::ImageFormat>
+ : public ContiguousEnumSerializer<mozilla::wr::ImageFormat,
+ mozilla::wr::ImageFormat::R8,
+ mozilla::wr::ImageFormat::Sentinel> {};
+
+template <>
+struct ParamTraits<mozilla::wr::LayoutSize>
+ : public PlainOldDataSerializer<mozilla::wr::LayoutSize> {};
+
+template <>
+struct ParamTraits<mozilla::wr::LayoutRect>
+ : public PlainOldDataSerializer<mozilla::wr::LayoutRect> {};
+
+template <>
+struct ParamTraits<mozilla::wr::LayoutPoint>
+ : public PlainOldDataSerializer<mozilla::wr::LayoutPoint> {};
+
+template <>
+struct ParamTraits<mozilla::wr::ImageRendering>
+ : public ContiguousEnumSerializer<mozilla::wr::ImageRendering,
+ mozilla::wr::ImageRendering::Auto,
+ mozilla::wr::ImageRendering::Sentinel> {};
+
+template <>
+struct ParamTraits<mozilla::wr::MixBlendMode>
+ : public ContiguousEnumSerializer<mozilla::wr::MixBlendMode,
+ mozilla::wr::MixBlendMode::Normal,
+ mozilla::wr::MixBlendMode::Sentinel> {};
+
+template <>
+struct ParamTraits<mozilla::wr::WebRenderError>
+ : public ContiguousEnumSerializer<mozilla::wr::WebRenderError,
+ mozilla::wr::WebRenderError::INITIALIZE,
+ mozilla::wr::WebRenderError::Sentinel> {};
+
+template <>
+struct ParamTraits<mozilla::wr::WrRotation>
+ : public ContiguousEnumSerializer<mozilla::wr::WrRotation,
+ mozilla::wr::WrRotation::Degree0,
+ mozilla::wr::WrRotation::Sentinel> {};
+
+template <>
+struct ParamTraits<mozilla::wr::MemoryReport>
+ : public PlainOldDataSerializer<mozilla::wr::MemoryReport> {};
+
+template <>
+struct ParamTraits<mozilla::wr::OpacityType>
+ : public PlainOldDataSerializer<mozilla::wr::OpacityType> {};
+
+template <>
+struct ParamTraits<mozilla::wr::ExternalImageKeyPair>
+ : public PlainOldDataSerializer<mozilla::wr::ExternalImageKeyPair> {};
+
+template <>
+struct ParamTraits<mozilla::wr::RenderReasons>
+ : public PlainOldDataSerializer<mozilla::wr::RenderReasons> {};
+
+template <>
+struct ParamTraits<mozilla::wr::ExternalImageSource>
+ : public ContiguousEnumSerializer<mozilla::wr::ExternalImageSource,
+ mozilla::wr::ExternalImageSource::Unknown,
+ mozilla::wr::ExternalImageSource::Last> {
+};
+
+} // namespace IPC
+
+#endif // GFX_WEBRENDERMESSAGEUTILS_H
diff --git a/gfx/layers/wr/WebRenderScrollData.cpp b/gfx/layers/wr/WebRenderScrollData.cpp
new file mode 100644
index 0000000000..538df8bdef
--- /dev/null
+++ b/gfx/layers/wr/WebRenderScrollData.cpp
@@ -0,0 +1,502 @@
+/* -*- 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/WebRenderScrollData.h"
+
+#include <ostream>
+
+#include "Units.h"
+#include "mozilla/layers/LayersMessageUtils.h"
+#include "mozilla/layers/WebRenderLayerManager.h"
+#include "mozilla/ToString.h"
+#include "mozilla/Unused.h"
+#include "nsDisplayList.h"
+#include "nsTArray.h"
+#include "UnitTransforms.h"
+
+namespace mozilla {
+namespace layers {
+
+WebRenderLayerScrollData::WebRenderLayerScrollData()
+ : mDescendantCount(-1),
+ mAncestorTransformId(ScrollableLayerGuid::NULL_SCROLL_ID),
+ mTransformIsPerspective(false),
+ mEventRegionsOverride(EventRegionsOverride::NoOverride),
+ mFixedPositionSides(mozilla::SideBits::eNone),
+ mFixedPosScrollContainerId(ScrollableLayerGuid::NULL_SCROLL_ID),
+ mStickyPosScrollContainerId(ScrollableLayerGuid::NULL_SCROLL_ID) {}
+
+WebRenderLayerScrollData::~WebRenderLayerScrollData() = default;
+
+void WebRenderLayerScrollData::InitializeRoot(int32_t aDescendantCount) {
+ mDescendantCount = aDescendantCount;
+}
+
+void WebRenderLayerScrollData::InitializeForTest(int32_t aDescendantCount) {
+ mDescendantCount = aDescendantCount;
+}
+
+void WebRenderLayerScrollData::Initialize(
+ WebRenderScrollData& aOwner, nsDisplayItem* aItem, int32_t aDescendantCount,
+ const ActiveScrolledRoot* aStopAtAsr,
+ const Maybe<gfx::Matrix4x4>& aAncestorTransform,
+ const ViewID& aAncestorTransformId) {
+ MOZ_ASSERT(aDescendantCount >= 0); // Ensure value is valid
+ MOZ_ASSERT(mDescendantCount ==
+ -1); // Don't allow re-setting an already set value
+ mDescendantCount = aDescendantCount;
+
+#if defined(DEBUG) || defined(MOZ_DUMP_PAINTING)
+ mInitializedFrom = aItem;
+#endif
+
+ MOZ_ASSERT(aItem);
+ aItem->UpdateScrollData(&aOwner, this);
+
+ const ActiveScrolledRoot* asr = aItem->GetActiveScrolledRoot();
+ if (ActiveScrolledRoot::IsAncestor(asr, aStopAtAsr)) {
+ // If the item's ASR is an ancestor of the stop-at ASR, then we don't need
+ // any more metrics information because we'll end up duplicating what the
+ // ancestor WebRenderLayerScrollData already has.
+ asr = nullptr;
+ }
+
+ while (asr && asr != aStopAtAsr) {
+ MOZ_ASSERT(aOwner.GetManager());
+ ScrollableLayerGuid::ViewID scrollId = asr->GetViewId();
+ if (Maybe<size_t> index = aOwner.HasMetadataFor(scrollId)) {
+ mScrollIds.AppendElement(index.ref());
+ } else {
+ Maybe<ScrollMetadata> metadata =
+ asr->mScrollableFrame->ComputeScrollMetadata(
+ aOwner.GetManager(), aItem->Frame(), aItem->ToReferenceFrame());
+ aOwner.GetBuilder()->AddScrollFrameToNotify(asr->mScrollableFrame);
+ if (metadata) {
+ MOZ_ASSERT(metadata->GetMetrics().GetScrollId() == scrollId);
+ mScrollIds.AppendElement(aOwner.AddMetadata(metadata.ref()));
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Expected scroll metadata to be available!");
+ }
+ }
+ asr = asr->mParent;
+ }
+
+#ifdef DEBUG
+ // Sanity check: if we have an ancestor transform, its scroll id should
+ // match one of the scroll metadatas on this node (WebRenderScrollDataWrapper
+ // will then use the ancestor transform at the level of that scroll metadata).
+ // One exception to this is if we have no scroll metadatas, which can happen
+ // if the scroll id of the transform is on an enclosing node.
+ if (aAncestorTransformId != ScrollableLayerGuid::NULL_SCROLL_ID &&
+ !mScrollIds.IsEmpty()) {
+ bool seenAncestorTransformId = false;
+ for (size_t scrollIdIndex : mScrollIds) {
+ if (aAncestorTransformId ==
+ aOwner.GetScrollMetadata(scrollIdIndex).GetMetrics().GetScrollId()) {
+ seenAncestorTransformId = true;
+ }
+ }
+ MOZ_ASSERT(
+ seenAncestorTransformId,
+ "The ancestor transform's view ID should match one of the metrics "
+ "on this node");
+ }
+#endif
+
+ // See the comments on StackingContextHelper::mDeferredTransformItem for an
+ // overview of what deferred transforms are.
+ // aAncestorTransform, if present, is the transform from a deferred transform
+ // item that is an ancestor of |aItem|. We store this transform value
+ // separately from mTransform because in the case where we have multiple
+ // scroll metadata on this layer item, the mAncestorTransform is associated
+ // with the "topmost" scroll metadata, and the mTransform is associated with
+ // the "bottommost" scroll metadata. The code in
+ // WebRenderScrollDataWrapper::GetTransform() is responsible for combining
+ // these transforms and exposing them appropriately. Also, we don't save the
+ // ancestor transform for thumb layers, because those are a special case in
+ // APZ; we need to keep the ancestor transform for the scrollable content that
+ // the thumb scrolls, but not for the thumb itself, as it will result in
+ // incorrect visual positioning of the thumb.
+ if (aAncestorTransform &&
+ mScrollbarData.mScrollbarLayerType != ScrollbarLayerType::Thumb) {
+ mAncestorTransform = *aAncestorTransform;
+ mAncestorTransformId = aAncestorTransformId;
+ }
+}
+
+int32_t WebRenderLayerScrollData::GetDescendantCount() const {
+ MOZ_ASSERT(mDescendantCount >= 0); // check that it was set
+ return mDescendantCount;
+}
+
+size_t WebRenderLayerScrollData::GetScrollMetadataCount() const {
+ return mScrollIds.Length();
+}
+
+void WebRenderLayerScrollData::AppendScrollMetadata(
+ WebRenderScrollData& aOwner, const ScrollMetadata& aData) {
+ mScrollIds.AppendElement(aOwner.AddMetadata(aData));
+}
+
+const ScrollMetadata& WebRenderLayerScrollData::GetScrollMetadata(
+ const WebRenderScrollData& aOwner, size_t aIndex) const {
+ MOZ_ASSERT(aIndex < mScrollIds.Length());
+ return aOwner.GetScrollMetadata(mScrollIds[aIndex]);
+}
+
+ScrollMetadata& WebRenderLayerScrollData::GetScrollMetadataMut(
+ WebRenderScrollData& aOwner, size_t aIndex) {
+ MOZ_ASSERT(aIndex < mScrollIds.Length());
+ return aOwner.GetScrollMetadataMut(mScrollIds[aIndex]);
+}
+
+CSSTransformMatrix WebRenderLayerScrollData::GetTransformTyped() const {
+ return ViewAs<CSSTransformMatrix>(GetTransform());
+}
+
+void WebRenderLayerScrollData::Dump(std::ostream& aOut,
+ const WebRenderScrollData& aOwner) const {
+ aOut << "WebRenderLayerScrollData(" << this
+ << "), descendantCount=" << mDescendantCount;
+#if defined(DEBUG) || defined(MOZ_DUMP_PAINTING)
+ if (mInitializedFrom) {
+ aOut << ", item=" << (void*)mInitializedFrom;
+ }
+#endif
+ if (mAsyncZoomContainerId) {
+ aOut << ", asyncZoomContainer";
+ }
+ for (size_t i = 0; i < mScrollIds.Length(); i++) {
+ aOut << ", metadata" << i << "=" << aOwner.GetScrollMetadata(mScrollIds[i]);
+ }
+ if (!mAncestorTransform.IsIdentity()) {
+ aOut << ", ancestorTransform=" << mAncestorTransform
+ << " (asr=" << mAncestorTransformId << ")";
+ }
+ if (!mTransform.IsIdentity()) {
+ aOut << ", transform=" << mTransform;
+ if (mTransformIsPerspective) {
+ aOut << ", transformIsPerspective";
+ }
+ }
+ aOut << ", visible=" << mVisibleRect;
+ if (mReferentId) {
+ aOut << ", refLayersId=" << *mReferentId;
+ }
+ if (mEventRegionsOverride) {
+ aOut << std::hex << ", eventRegionsOverride=0x"
+ << (int)mEventRegionsOverride << std::dec;
+ }
+ if (mScrollbarData.mScrollbarLayerType != ScrollbarLayerType::None) {
+ aOut << ", scrollbarType=" << (int)mScrollbarData.mScrollbarLayerType
+ << std::hex << ", scrollbarAnimationId=0x"
+ << mScrollbarAnimationId.valueOr(0) << std::dec;
+ }
+ if (mFixedPosScrollContainerId != ScrollableLayerGuid::NULL_SCROLL_ID) {
+ aOut << ", fixedContainer=" << mFixedPosScrollContainerId << std::hex
+ << ", fixedAnimation=0x" << mFixedPositionAnimationId.valueOr(0)
+ << ", sideBits=0x" << (int)mFixedPositionSides << std::dec;
+ }
+ if (mStickyPosScrollContainerId != ScrollableLayerGuid::NULL_SCROLL_ID) {
+ aOut << ", stickyContainer=" << mStickyPosScrollContainerId << std::hex
+ << ", stickyAnimation=" << mStickyPositionAnimationId.valueOr(0)
+ << std::dec << ", stickyInner=" << mStickyScrollRangeInner
+ << ", stickyOuter=" << mStickyScrollRangeOuter;
+ }
+}
+
+WebRenderScrollData::WebRenderScrollData()
+ : mManager(nullptr),
+ mBuilder(nullptr),
+ mIsFirstPaint(false),
+ mPaintSequenceNumber(0) {}
+
+WebRenderScrollData::WebRenderScrollData(WebRenderLayerManager* aManager,
+ nsDisplayListBuilder* aBuilder)
+ : mManager(aManager),
+ mBuilder(aBuilder),
+ mIsFirstPaint(false),
+ mPaintSequenceNumber(0) {}
+
+bool WebRenderScrollData::Validate() const {
+ // Attempt to traverse the tree structure encoded by the descendant counts,
+ // validating as we go that everything is within bounds and properly nested.
+ // In addition, check that the traversal visits every node exactly once.
+ std::vector<size_t> visitCounts(mLayerScrollData.Length(), 0);
+ if (mLayerScrollData.Length() > 0) {
+ if (!mLayerScrollData[0].ValidateSubtree(*this, visitCounts, 0)) {
+ return false;
+ }
+ }
+ for (size_t visitCount : visitCounts) {
+ if (visitCount != 1) {
+ return false;
+ }
+ }
+ return true;
+}
+
+WebRenderLayerManager* WebRenderScrollData::GetManager() const {
+ return mManager;
+}
+
+nsDisplayListBuilder* WebRenderScrollData::GetBuilder() const {
+ return mBuilder;
+}
+
+size_t WebRenderScrollData::AddMetadata(const ScrollMetadata& aMetadata) {
+ ScrollableLayerGuid::ViewID scrollId = aMetadata.GetMetrics().GetScrollId();
+ auto p = mScrollIdMap.lookupForAdd(scrollId);
+ if (!p) {
+ // It's a scrollId we hadn't seen before
+ bool ok = mScrollIdMap.add(p, scrollId, mScrollMetadatas.Length());
+ MOZ_RELEASE_ASSERT(ok);
+ mScrollMetadatas.AppendElement(aMetadata);
+ } // else we didn't insert, because it already existed
+ return p->value();
+}
+
+size_t WebRenderScrollData::AddLayerData(WebRenderLayerScrollData&& aData) {
+ mLayerScrollData.AppendElement(std::move(aData));
+ return mLayerScrollData.Length() - 1;
+}
+
+size_t WebRenderScrollData::GetLayerCount() const {
+ return mLayerScrollData.Length();
+}
+
+bool WebRenderLayerScrollData::ValidateSubtree(
+ const WebRenderScrollData& aParent, std::vector<size_t>& aVisitCounts,
+ size_t aCurrentIndex) const {
+ ++aVisitCounts[aCurrentIndex];
+
+ // All scroll ids must be in bounds.
+ for (size_t scrollMetadataIndex : mScrollIds) {
+ if (scrollMetadataIndex >= aParent.mScrollMetadatas.Length()) {
+ return false;
+ }
+ }
+
+ // Descendant count must be nonnegative.
+ if (mDescendantCount < 0) {
+ return false;
+ }
+ size_t descendantCount = static_cast<size_t>(mDescendantCount);
+
+ // Bounds check: for every layer, its index + its mDescendantCount
+ // must be within bounds.
+ if (aCurrentIndex + descendantCount >= aParent.mLayerScrollData.Length()) {
+ return false;
+ }
+
+ // Recurse over our children, accumulating a count of our children
+ // and their descendants as we go.
+ size_t childCount = 0;
+ size_t childDescendantCounts = 0;
+ size_t currentChildIndex = aCurrentIndex + 1;
+ while (currentChildIndex < (aCurrentIndex + descendantCount + 1)) {
+ ++childCount;
+
+ const WebRenderLayerScrollData* currentChild =
+ &aParent.mLayerScrollData[currentChildIndex];
+ childDescendantCounts += currentChild->mDescendantCount;
+ currentChild->ValidateSubtree(aParent, aVisitCounts, currentChildIndex);
+
+ // The current child's descendants come first in the array, and the next
+ // element after that is our next child.
+ currentChildIndex += (currentChild->mDescendantCount + 1);
+ }
+
+ // For a given layer, its descendant count must equal the number of
+ // children + the descendant counts of its children added together.
+ return descendantCount == (childCount + childDescendantCounts);
+}
+
+const WebRenderLayerScrollData* WebRenderScrollData::GetLayerData(
+ size_t aIndex) const {
+ if (aIndex >= mLayerScrollData.Length()) {
+ return nullptr;
+ }
+ return &(mLayerScrollData.ElementAt(aIndex));
+}
+
+WebRenderLayerScrollData* WebRenderScrollData::GetLayerData(size_t aIndex) {
+ if (aIndex >= mLayerScrollData.Length()) {
+ return nullptr;
+ }
+ return &(mLayerScrollData.ElementAt(aIndex));
+}
+
+const ScrollMetadata& WebRenderScrollData::GetScrollMetadata(
+ size_t aIndex) const {
+ MOZ_ASSERT(aIndex < mScrollMetadatas.Length());
+ return mScrollMetadatas[aIndex];
+}
+
+ScrollMetadata& WebRenderScrollData::GetScrollMetadataMut(size_t aIndex) {
+ MOZ_ASSERT(aIndex < mScrollMetadatas.Length());
+ return mScrollMetadatas[aIndex];
+}
+
+Maybe<size_t> WebRenderScrollData::HasMetadataFor(
+ const ScrollableLayerGuid::ViewID& aScrollId) const {
+ auto ptr = mScrollIdMap.lookup(aScrollId);
+ return (ptr ? Some(ptr->value()) : Nothing());
+}
+
+void WebRenderScrollData::SetIsFirstPaint() { mIsFirstPaint = true; }
+
+bool WebRenderScrollData::IsFirstPaint() const { return mIsFirstPaint; }
+
+void WebRenderScrollData::SetPaintSequenceNumber(
+ uint32_t aPaintSequenceNumber) {
+ mPaintSequenceNumber = aPaintSequenceNumber;
+}
+
+uint32_t WebRenderScrollData::GetPaintSequenceNumber() const {
+ return mPaintSequenceNumber;
+}
+
+void WebRenderScrollData::ApplyUpdates(ScrollUpdatesMap&& aUpdates,
+ uint32_t aPaintSequenceNumber) {
+ for (auto it = aUpdates.Iter(); !it.Done(); it.Next()) {
+ if (Maybe<size_t> index = HasMetadataFor(it.Key())) {
+ mScrollMetadatas[*index].UpdatePendingScrollInfo(std::move(it.Data()));
+ }
+ }
+ mPaintSequenceNumber = aPaintSequenceNumber;
+}
+
+void WebRenderScrollData::DumpSubtree(std::ostream& aOut, size_t aIndex,
+ const std::string& aIndent) const {
+ aOut << aIndent;
+ mLayerScrollData.ElementAt(aIndex).Dump(aOut, *this);
+ aOut << std::endl;
+
+ int32_t descendants = mLayerScrollData.ElementAt(aIndex).GetDescendantCount();
+ if (descendants == 0) {
+ return;
+ }
+
+ // Build a stack of indices at which this aIndex's children live. We do
+ // this because we want to dump them first-to-last but they are stored
+ // last-to-first.
+ std::stack<size_t> childIndices;
+ size_t childIndex = aIndex + 1;
+ while (descendants > 0) {
+ childIndices.push(childIndex);
+ // "1" for the child itelf, plus whatever descendants it has
+ int32_t subtreeSize =
+ 1 + mLayerScrollData.ElementAt(childIndex).GetDescendantCount();
+ childIndex += subtreeSize;
+ descendants -= subtreeSize;
+ MOZ_ASSERT(descendants >= 0);
+ }
+
+ std::string indent = aIndent + " ";
+ while (!childIndices.empty()) {
+ size_t child = childIndices.top();
+ childIndices.pop();
+ DumpSubtree(aOut, child, indent);
+ }
+}
+
+std::ostream& operator<<(std::ostream& aOut, const WebRenderScrollData& aData) {
+ aOut << "--- WebRenderScrollData (firstPaint=" << aData.mIsFirstPaint
+ << ") ---" << std::endl;
+
+ if (aData.mLayerScrollData.Length() > 0) {
+ aData.DumpSubtree(aOut, 0, std::string());
+ }
+ return aOut;
+}
+
+bool WebRenderScrollData::RepopulateMap() {
+ MOZ_ASSERT(mScrollIdMap.empty());
+ for (size_t i = 0; i < mScrollMetadatas.Length(); i++) {
+ ScrollableLayerGuid::ViewID scrollId =
+ mScrollMetadatas[i].GetMetrics().GetScrollId();
+ bool ok = mScrollIdMap.putNew(scrollId, i);
+ MOZ_RELEASE_ASSERT(ok);
+ }
+ return true;
+}
+
+} // namespace layers
+} // namespace mozilla
+
+namespace IPC {
+
+void ParamTraits<mozilla::layers::WebRenderLayerScrollData>::Write(
+ MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.mDescendantCount);
+ WriteParam(aWriter, aParam.mScrollIds);
+ WriteParam(aWriter, aParam.mAncestorTransform);
+ WriteParam(aWriter, aParam.mAncestorTransformId);
+ WriteParam(aWriter, aParam.mTransform);
+ WriteParam(aWriter, aParam.mTransformIsPerspective);
+ WriteParam(aWriter, aParam.mVisibleRect);
+ WriteParam(aWriter, aParam.mRemoteDocumentSize);
+ WriteParam(aWriter, aParam.mReferentId);
+ WriteParam(aWriter, aParam.mEventRegionsOverride);
+ WriteParam(aWriter, aParam.mScrollbarData);
+ WriteParam(aWriter, aParam.mScrollbarAnimationId);
+ WriteParam(aWriter, aParam.mFixedPositionAnimationId);
+ WriteParam(aWriter, aParam.mFixedPositionSides);
+ WriteParam(aWriter, aParam.mFixedPosScrollContainerId);
+ WriteParam(aWriter, aParam.mStickyPosScrollContainerId);
+ WriteParam(aWriter, aParam.mStickyScrollRangeOuter);
+ WriteParam(aWriter, aParam.mStickyScrollRangeInner);
+ WriteParam(aWriter, aParam.mStickyPositionAnimationId);
+ WriteParam(aWriter, aParam.mZoomAnimationId);
+ WriteParam(aWriter, aParam.mAsyncZoomContainerId);
+ // Do not write |mInitializedFrom|, the pointer wouldn't be valid
+ // on the compositor side.
+}
+
+bool ParamTraits<mozilla::layers::WebRenderLayerScrollData>::Read(
+ MessageReader* aReader, paramType* aResult) {
+ return ReadParam(aReader, &aResult->mDescendantCount) &&
+ ReadParam(aReader, &aResult->mScrollIds) &&
+ ReadParam(aReader, &aResult->mAncestorTransform) &&
+ ReadParam(aReader, &aResult->mAncestorTransformId) &&
+ ReadParam(aReader, &aResult->mTransform) &&
+ ReadParam(aReader, &aResult->mTransformIsPerspective) &&
+ ReadParam(aReader, &aResult->mVisibleRect) &&
+ ReadParam(aReader, &aResult->mRemoteDocumentSize) &&
+ ReadParam(aReader, &aResult->mReferentId) &&
+ ReadParam(aReader, &aResult->mEventRegionsOverride) &&
+ ReadParam(aReader, &aResult->mScrollbarData) &&
+ ReadParam(aReader, &aResult->mScrollbarAnimationId) &&
+ ReadParam(aReader, &aResult->mFixedPositionAnimationId) &&
+ ReadParam(aReader, &aResult->mFixedPositionSides) &&
+ ReadParam(aReader, &aResult->mFixedPosScrollContainerId) &&
+ ReadParam(aReader, &aResult->mStickyPosScrollContainerId) &&
+ ReadParam(aReader, &aResult->mStickyScrollRangeOuter) &&
+ ReadParam(aReader, &aResult->mStickyScrollRangeInner) &&
+ ReadParam(aReader, &aResult->mStickyPositionAnimationId) &&
+ ReadParam(aReader, &aResult->mZoomAnimationId) &&
+ ReadParam(aReader, &aResult->mAsyncZoomContainerId);
+}
+
+void ParamTraits<mozilla::layers::WebRenderScrollData>::Write(
+ MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.mScrollMetadatas);
+ WriteParam(aWriter, aParam.mLayerScrollData);
+ WriteParam(aWriter, aParam.mIsFirstPaint);
+ WriteParam(aWriter, aParam.mPaintSequenceNumber);
+}
+
+bool ParamTraits<mozilla::layers::WebRenderScrollData>::Read(
+ MessageReader* aReader, paramType* aResult) {
+ return ReadParam(aReader, &aResult->mScrollMetadatas) &&
+ ReadParam(aReader, &aResult->mLayerScrollData) &&
+ ReadParam(aReader, &aResult->mIsFirstPaint) &&
+ ReadParam(aReader, &aResult->mPaintSequenceNumber) &&
+ aResult->RepopulateMap();
+}
+
+} // namespace IPC
diff --git a/gfx/layers/wr/WebRenderScrollData.h b/gfx/layers/wr/WebRenderScrollData.h
new file mode 100644
index 0000000000..c575d4ca21
--- /dev/null
+++ b/gfx/layers/wr/WebRenderScrollData.h
@@ -0,0 +1,358 @@
+/* -*- 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/. */
+
+#ifndef GFX_WEBRENDERSCROLLDATA_H
+#define GFX_WEBRENDERSCROLLDATA_H
+
+#include <map>
+#include <iosfwd>
+
+#include "chrome/common/ipc_message_utils.h"
+#include "FrameMetrics.h"
+#include "ipc/IPCMessageUtils.h"
+#include "LayersTypes.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/GfxMessageUtils.h"
+#include "mozilla/layers/FocusTarget.h"
+#include "mozilla/layers/ScrollbarData.h"
+#include "mozilla/layers/WebRenderMessageUtils.h"
+#include "mozilla/webrender/WebRenderTypes.h"
+#include "mozilla/HashTable.h"
+#include "mozilla/Maybe.h"
+#include "nsTArrayForwardDeclare.h"
+
+namespace mozilla {
+
+class nsDisplayItem;
+class nsDisplayListBuilder;
+struct ActiveScrolledRoot;
+
+namespace layers {
+
+class APZTestAccess;
+class Layer;
+class WebRenderLayerManager;
+class WebRenderScrollData;
+
+// Data needed by APZ, per layer. One instance of this class is created for
+// each layer in the layer tree and sent over PWebRenderBridge to the APZ code.
+// Each WebRenderLayerScrollData is conceptually associated with an "owning"
+// WebRenderScrollData.
+class WebRenderLayerScrollData final {
+ public:
+ WebRenderLayerScrollData(); // needed for IPC purposes
+ WebRenderLayerScrollData(WebRenderLayerScrollData&& aOther) = default;
+ ~WebRenderLayerScrollData();
+
+ using ViewID = ScrollableLayerGuid::ViewID;
+
+ // Helper function for WebRenderScrollData::Validate().
+ bool ValidateSubtree(const WebRenderScrollData& aParent,
+ std::vector<size_t>& aVisitCounts,
+ size_t aCurrentIndex) const;
+
+ void InitializeRoot(int32_t aDescendantCount);
+ void Initialize(WebRenderScrollData& aOwner, nsDisplayItem* aItem,
+ int32_t aDescendantCount,
+ const ActiveScrolledRoot* aStopAtAsr,
+ const Maybe<gfx::Matrix4x4>& aAncestorTransform,
+ const ViewID& aAncestorTransformId);
+
+ int32_t GetDescendantCount() const;
+ size_t GetScrollMetadataCount() const;
+
+ void AppendScrollMetadata(WebRenderScrollData& aOwner,
+ const ScrollMetadata& aData);
+ // Return the ScrollMetadata object that used to be on the original Layer
+ // at the given index. Since we deduplicate the ScrollMetadata objects into
+ // the array in the owning WebRenderScrollData object, we need to be passed
+ // in a reference to that owner as well.
+ const ScrollMetadata& GetScrollMetadata(const WebRenderScrollData& aOwner,
+ size_t aIndex) const;
+
+ gfx::Matrix4x4 GetAncestorTransform() const { return mAncestorTransform; }
+ ViewID GetAncestorTransformId() const { return mAncestorTransformId; }
+ void SetTransform(const gfx::Matrix4x4& aTransform) {
+ mTransform = aTransform;
+ }
+ gfx::Matrix4x4 GetTransform() const { return mTransform; }
+ CSSTransformMatrix GetTransformTyped() const;
+ void SetTransformIsPerspective(bool aTransformIsPerspective) {
+ mTransformIsPerspective = aTransformIsPerspective;
+ }
+ bool GetTransformIsPerspective() const { return mTransformIsPerspective; }
+
+ void SetEventRegionsOverride(const EventRegionsOverride& aOverride) {
+ mEventRegionsOverride = aOverride;
+ }
+ EventRegionsOverride GetEventRegionsOverride() const {
+ return mEventRegionsOverride;
+ }
+
+ void SetVisibleRect(const LayerIntRect& aRect) { mVisibleRect = aRect; }
+ const LayerIntRect& GetVisibleRect() const { return mVisibleRect; }
+ void SetRemoteDocumentSize(const LayerIntSize& aRemoteDocumentSize) {
+ mRemoteDocumentSize = aRemoteDocumentSize;
+ }
+ const LayerIntSize& GetRemoteDocumentSize() const {
+ return mRemoteDocumentSize;
+ }
+ void SetReferentId(LayersId aReferentId) { mReferentId = Some(aReferentId); }
+ Maybe<LayersId> GetReferentId() const { return mReferentId; }
+
+ void SetScrollbarData(const ScrollbarData& aData) { mScrollbarData = aData; }
+ const ScrollbarData& GetScrollbarData() const { return mScrollbarData; }
+ void SetScrollbarAnimationId(const uint64_t& aId) {
+ mScrollbarAnimationId = Some(aId);
+ }
+ Maybe<uint64_t> GetScrollbarAnimationId() const {
+ return mScrollbarAnimationId;
+ }
+
+ void SetFixedPositionAnimationId(const uint64_t& aId) {
+ mFixedPositionAnimationId = Some(aId);
+ }
+ Maybe<uint64_t> GetFixedPositionAnimationId() const {
+ return mFixedPositionAnimationId;
+ }
+
+ void SetFixedPositionSides(const SideBits& aSideBits) {
+ mFixedPositionSides = aSideBits;
+ }
+ SideBits GetFixedPositionSides() const { return mFixedPositionSides; }
+
+ void SetFixedPositionScrollContainerId(ViewID aId) {
+ mFixedPosScrollContainerId = aId;
+ }
+ ViewID GetFixedPositionScrollContainerId() const {
+ return mFixedPosScrollContainerId;
+ }
+
+ void SetStickyPositionScrollContainerId(ViewID aId) {
+ mStickyPosScrollContainerId = aId;
+ }
+ ViewID GetStickyPositionScrollContainerId() const {
+ return mStickyPosScrollContainerId;
+ }
+
+ void SetStickyScrollRangeOuter(const LayerRectAbsolute& scrollRange) {
+ mStickyScrollRangeOuter = scrollRange;
+ }
+ const LayerRectAbsolute& GetStickyScrollRangeOuter() const {
+ return mStickyScrollRangeOuter;
+ }
+
+ void SetStickyScrollRangeInner(const LayerRectAbsolute& scrollRange) {
+ mStickyScrollRangeInner = scrollRange;
+ }
+ const LayerRectAbsolute& GetStickyScrollRangeInner() const {
+ return mStickyScrollRangeInner;
+ }
+
+ void SetStickyPositionAnimationId(const uint64_t& aId) {
+ mStickyPositionAnimationId = Some(aId);
+ }
+ Maybe<uint64_t> GetStickyPositionAnimationId() const {
+ return mStickyPositionAnimationId;
+ }
+
+ void SetZoomAnimationId(const uint64_t& aId) { mZoomAnimationId = Some(aId); }
+ Maybe<uint64_t> GetZoomAnimationId() const { return mZoomAnimationId; }
+
+ void SetAsyncZoomContainerId(const ViewID& aId) {
+ mAsyncZoomContainerId = Some(aId);
+ }
+ Maybe<ViewID> GetAsyncZoomContainerId() const {
+ return mAsyncZoomContainerId;
+ }
+
+ void Dump(std::ostream& aOut, const WebRenderScrollData& aOwner) const;
+
+ friend struct IPC::ParamTraits<WebRenderLayerScrollData>;
+
+ private:
+ // For test use only
+ friend class APZTestAccess;
+
+ // For use by GTests in building WebRenderLayerScrollData trees.
+ // GTests don't have a display list so they can't use Initialize().
+ void InitializeForTest(int32_t aDescendantCount);
+
+ ScrollMetadata& GetScrollMetadataMut(WebRenderScrollData& aOwner,
+ size_t aIndex);
+
+ private:
+ // The number of descendants this layer has (not including the layer itself).
+ // This is needed to reconstruct the depth-first layer tree traversal
+ // efficiently. Leaf layers should always have 0 descendants.
+ int32_t mDescendantCount;
+
+ // Handles to the ScrollMetadata objects that were on this layer. The values
+ // stored in this array are indices into the owning WebRenderScrollData's
+ // mScrollMetadatas array. This indirection is used to deduplicate the
+ // ScrollMetadata objects, since there is usually heavy duplication of them
+ // within a layer tree.
+ CopyableTArray<size_t> mScrollIds;
+
+ // Various data that we collect from the Layer in Initialize(), serialize
+ // over IPC, and use on the parent side in APZ.
+
+ gfx::Matrix4x4 mAncestorTransform;
+ ViewID mAncestorTransformId;
+ gfx::Matrix4x4 mTransform;
+ bool mTransformIsPerspective;
+ LayerIntRect mVisibleRect;
+ // The remote documents only need their size because their origin is always
+ // (0, 0).
+ LayerIntSize mRemoteDocumentSize;
+ Maybe<LayersId> mReferentId;
+ EventRegionsOverride mEventRegionsOverride;
+ ScrollbarData mScrollbarData;
+ Maybe<uint64_t> mScrollbarAnimationId;
+ Maybe<uint64_t> mFixedPositionAnimationId;
+ SideBits mFixedPositionSides;
+ ViewID mFixedPosScrollContainerId;
+ ViewID mStickyPosScrollContainerId;
+ LayerRectAbsolute mStickyScrollRangeOuter;
+ LayerRectAbsolute mStickyScrollRangeInner;
+ Maybe<uint64_t> mStickyPositionAnimationId;
+ Maybe<uint64_t> mZoomAnimationId;
+ Maybe<ViewID> mAsyncZoomContainerId;
+
+#if defined(DEBUG) || defined(MOZ_DUMP_PAINTING)
+ // The display item for which this layer was built.
+ // This is only set on the content side.
+ nsDisplayItem* mInitializedFrom = nullptr;
+#endif
+};
+
+// Data needed by APZ, for the whole layer tree. One instance of this class
+// is created for each transaction sent over PWebRenderBridge. It is populated
+// with information from the WebRender layer tree on the client side and the
+// information is used by APZ on the parent side.
+class WebRenderScrollData {
+ public:
+ WebRenderScrollData();
+ explicit WebRenderScrollData(WebRenderLayerManager* aManager,
+ nsDisplayListBuilder* aBuilder);
+ WebRenderScrollData(WebRenderScrollData&& aOther) = default;
+ WebRenderScrollData& operator=(WebRenderScrollData&& aOther) = default;
+ virtual ~WebRenderScrollData() = default;
+
+ // Validate that the scroll data is well-formed, and particularly that
+ // |mLayerScrollData| encodes a valid tree. This is necessary because
+ // the data can be sent over IPC from a less-trusted content process.
+ bool Validate() const;
+
+ WebRenderLayerManager* GetManager() const;
+
+ nsDisplayListBuilder* GetBuilder() const;
+
+ // Add the given ScrollMetadata if it doesn't already exist. Return an index
+ // that can be used to look up the metadata later.
+ size_t AddMetadata(const ScrollMetadata& aMetadata);
+ // Add the provided WebRenderLayerScrollData and return the index that can
+ // be used to look it up via GetLayerData.
+ size_t AddLayerData(WebRenderLayerScrollData&& aData);
+
+ size_t GetLayerCount() const;
+
+ // Return a pointer to the scroll data at the given index. Use with caution,
+ // as the pointer may be invalidated if this WebRenderScrollData is mutated.
+ const WebRenderLayerScrollData* GetLayerData(size_t aIndex) const;
+ WebRenderLayerScrollData* GetLayerData(size_t aIndex);
+
+ const ScrollMetadata& GetScrollMetadata(size_t aIndex) const;
+ Maybe<size_t> HasMetadataFor(
+ const ScrollableLayerGuid::ViewID& aScrollId) const;
+
+ void SetIsFirstPaint();
+ bool IsFirstPaint() const;
+ void SetPaintSequenceNumber(uint32_t aPaintSequenceNumber);
+ uint32_t GetPaintSequenceNumber() const;
+
+ void ApplyUpdates(ScrollUpdatesMap&& aUpdates, uint32_t aPaintSequenceNumber);
+
+ friend struct IPC::ParamTraits<WebRenderScrollData>;
+
+ friend std::ostream& operator<<(std::ostream& aOut,
+ const WebRenderScrollData& aData);
+
+ private:
+ // For test use only.
+ friend class WebRenderLayerScrollData;
+ ScrollMetadata& GetScrollMetadataMut(size_t aIndex);
+
+ private:
+ // This is called by the ParamTraits implementation to rebuild mScrollIdMap
+ // based on mScrollMetadatas
+ bool RepopulateMap();
+
+ // This is a helper for the dumping code
+ void DumpSubtree(std::ostream& aOut, size_t aIndex,
+ const std::string& aIndent) const;
+
+ private:
+ // Pointer back to the layer manager; if this is non-null, it will always be
+ // valid, because the WebRenderLayerManager that created |this| will
+ // outlive |this|.
+ WebRenderLayerManager* MOZ_NON_OWNING_REF mManager;
+
+ // Pointer to the display list builder; if this is non-null, it will always be
+ // valid, because the nsDisplayListBuilder that created the layer manager will
+ // outlive |this|.
+ nsDisplayListBuilder* MOZ_NON_OWNING_REF mBuilder;
+
+ // Internal data structure used to maintain uniqueness of mScrollMetadatas.
+ // This is not serialized/deserialized over IPC, but it is rebuilt on the
+ // parent side when mScrollMetadatas is deserialized. So it should always be
+ // valid on both the child and parent.
+ // The key into this map is the scrollId of a ScrollMetadata, and the value is
+ // an index into the mScrollMetadatas array.
+ HashMap<ScrollableLayerGuid::ViewID, size_t> mScrollIdMap;
+
+ // A list of all the unique ScrollMetadata objects from the layer tree. Each
+ // ScrollMetadata in this list must have a unique scroll id.
+ nsTArray<ScrollMetadata> mScrollMetadatas;
+
+ // A list of per-layer scroll data objects, generated via a depth-first,
+ // pre-order, last-to-first traversal of the layer tree (i.e. a recursive
+ // traversal where a node N first pushes itself, followed by its children in
+ // last-to-first order). Each layer's scroll data object knows how many
+ // descendants that layer had, which allows reconstructing the traversal on
+ // the other side.
+ nsTArray<WebRenderLayerScrollData> mLayerScrollData;
+
+ bool mIsFirstPaint;
+ uint32_t mPaintSequenceNumber;
+};
+
+} // namespace layers
+} // namespace mozilla
+
+namespace IPC {
+
+template <>
+struct ParamTraits<mozilla::layers::WebRenderLayerScrollData> {
+ typedef mozilla::layers::WebRenderLayerScrollData paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam);
+
+ static bool Read(MessageReader* aReader, paramType* aResult);
+};
+
+template <>
+struct ParamTraits<mozilla::layers::WebRenderScrollData> {
+ typedef mozilla::layers::WebRenderScrollData paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam);
+
+ static bool Read(MessageReader* aReader, paramType* aResult);
+};
+
+} // namespace IPC
+
+#endif /* GFX_WEBRENDERSCROLLDATA_H */
diff --git a/gfx/layers/wr/WebRenderScrollDataWrapper.h b/gfx/layers/wr/WebRenderScrollDataWrapper.h
new file mode 100644
index 0000000000..31693218d8
--- /dev/null
+++ b/gfx/layers/wr/WebRenderScrollDataWrapper.h
@@ -0,0 +1,536 @@
+/* -*- 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/. */
+
+#ifndef GFX_WEBRENDERSCROLLDATAWRAPPER_H
+#define GFX_WEBRENDERSCROLLDATAWRAPPER_H
+
+#include "FrameMetrics.h"
+#include "mozilla/layers/APZUpdater.h"
+#include "mozilla/layers/CompositorBridgeParent.h"
+#include "mozilla/layers/WebRenderBridgeParent.h"
+#include "mozilla/layers/WebRenderScrollData.h"
+
+namespace mozilla {
+namespace layers {
+
+/**
+ * A wrapper class around a target WebRenderLayerScrollData (henceforth,
+ * "layer") that allows user code to walk through the ScrollMetadata objects
+ * on the layer the same way it would walk through a layer tree.
+ * Consider the following layer tree:
+ *
+ * +---+
+ * | A |
+ * +---+
+ * / | \
+ * / | \
+ * / | \
+ * +---+ +-----+ +---+
+ * | B | | C | | D |
+ * +---+ +-----+ +---+
+ * | SMn |
+ * | . |
+ * | . |
+ * | . |
+ * | SM1 |
+ * | SM0 |
+ * +-----+
+ * / \
+ * / \
+ * +---+ +---+
+ * | E | | F |
+ * +---+ +---+
+ *
+ * In this layer tree, there are six layers with A being the root and B,D,E,F
+ * being leaf nodes. Layer C is in the middle and has n+1 ScrollMetadata,
+ * labelled SM0...SMn. SM0 is the ScrollMetadata you get by calling
+ * c->GetScrollMetadata(0) and SMn is the ScrollMetadata you can obtain by
+ * calling c->GetScrollMetadata(c->GetScrollMetadataCount() - 1). This layer
+ * tree is conceptually equivalent to this one below:
+ *
+ * +---+
+ * | A |
+ * +---+
+ * / | \
+ * / | \
+ * / | \
+ * +---+ +-----+ +---+
+ * | B | | Cn | | D |
+ * +---+ +-----+ +---+
+ * |
+ * .
+ * .
+ * .
+ * |
+ * +-----+
+ * | C1 |
+ * +-----+
+ * |
+ * +-----+
+ * | C0 |
+ * +-----+
+ * / \
+ * / \
+ * +---+ +---+
+ * | E | | F |
+ * +---+ +---+
+ *
+ * In this layer tree, the layer C has been expanded into a stack of layers
+ * C1...Cn, where C1 has ScrollMetadata SM1 and Cn has ScrollMetdata Fn.
+ *
+ * The WebRenderScrollDataWrapper class allows client code to treat the first
+ * layer tree as though it were the second. That is, instead of client code
+ * having to iterate through the ScrollMetadata objects directly, it can use a
+ * WebRenderScrollDataWrapper to encapsulate that aspect of the layer tree and
+ * just walk the tree as if it were a stack of layers.
+ *
+ * The functions on this class do different things depending on which
+ * simulated layer is being wrapped. For example, if the
+ * WebRenderScrollDataWrapper is pretending to be C0, the GetPrevSibling()
+ * function will return null even though the underlying layer C does actually
+ * have a prev sibling. The WebRenderScrollDataWrapper pretending to be Cn will
+ * return B as the prev sibling.
+ *
+ * Implementation notes:
+ *
+ * The AtTopLayer() and AtBottomLayer() functions in this class refer to
+ * Cn and C0 in the second layer tree above; that is, they are predicates
+ * to test if the wrapper is simulating the topmost or bottommost layer, as
+ * those can have special behaviour.
+ *
+ * It is possible to wrap a nullptr in a WebRenderScrollDataWrapper, in which
+ * case the IsValid() function will return false. This is required to allow
+ * WebRenderScrollDataWrapper to be a MOZ_STACK_CLASS (desirable because it is
+ * used in loops and recursion).
+ *
+ * This class purposely does not expose the wrapped layer directly to avoid
+ * user code from accidentally calling functions directly on it. Instead
+ * any necessary functions should be wrapped in this class. It does expose
+ * the wrapped layer as a void* for printf purposes.
+ *
+ * The implementation may look like it special-cases mIndex == 0 and/or
+ * GetScrollMetadataCount() == 0. This is an artifact of the fact that both
+ * mIndex and GetScrollMetadataCount() are uint32_t and GetScrollMetadataCount()
+ * can return 0 but mIndex cannot store -1. This seems better than the
+ * alternative of making mIndex a int32_t that can store -1, but then having
+ * to cast to uint32_t all over the place.
+ *
+ * Note that WebRenderLayerScrollData objects are owned by WebRenderScrollData,
+ * which stores them in a flattened representation. The field mData,
+ * mLayerIndex, and mContainingSubtreeIndex are used to move around the "layers"
+ * given the flattened representation. The mMetadataIndex is used to move around
+ * the ScrollMetadata within a single layer.
+ *
+ * One important note here is that this class holds a pointer to the "owning"
+ * WebRenderScrollData. The caller must ensure that this class does not outlive
+ * the owning WebRenderScrollData, or this may result in use-after-free errors.
+ * This class being declared a MOZ_STACK_CLASS should help with that.
+ */
+class MOZ_STACK_CLASS WebRenderScrollDataWrapper final {
+ public:
+ // Basic constructor for external callers. Starts the walker at the root of
+ // the tree.
+ explicit WebRenderScrollDataWrapper(
+ const APZUpdater& aUpdater, const WebRenderScrollData* aData = nullptr)
+ : mUpdater(&aUpdater),
+ mData(aData),
+ mLayerIndex(0),
+ mContainingSubtreeLastIndex(0),
+ mLayer(nullptr),
+ mMetadataIndex(0) {
+ if (!mData) {
+ return;
+ }
+ mLayer = mData->GetLayerData(mLayerIndex);
+ if (!mLayer) {
+ return;
+ }
+
+ // sanity check on the data
+ MOZ_ASSERT(mData->GetLayerCount() ==
+ (size_t)(1 + mLayer->GetDescendantCount()));
+ mContainingSubtreeLastIndex = mData->GetLayerCount();
+
+ // See documentation in LayerMetricsWrapper.h about this. mMetadataIndex
+ // in this class is equivalent to mIndex in that class.
+ mMetadataIndex = mLayer->GetScrollMetadataCount();
+ if (mMetadataIndex > 0) {
+ mMetadataIndex--;
+ }
+ }
+
+ private:
+ // Internal constructor for walking from one WebRenderLayerScrollData to
+ // another. In this case we need to recompute the mMetadataIndex to be the
+ // "topmost" scroll metadata on the new layer.
+ WebRenderScrollDataWrapper(const APZUpdater* aUpdater,
+ const WebRenderScrollData* aData,
+ size_t aLayerIndex,
+ size_t aContainingSubtreeLastIndex)
+ : mUpdater(aUpdater),
+ mData(aData),
+ mLayerIndex(aLayerIndex),
+ mContainingSubtreeLastIndex(aContainingSubtreeLastIndex),
+ mLayer(nullptr),
+ mMetadataIndex(0) {
+ MOZ_ASSERT(mData);
+ mLayer = mData->GetLayerData(mLayerIndex);
+ MOZ_ASSERT(mLayer);
+
+ // See documentation in LayerMetricsWrapper.h about this. mMetadataIndex
+ // in this class is equivalent to mIndex in that class.
+ mMetadataIndex = mLayer->GetScrollMetadataCount();
+ if (mMetadataIndex > 0) {
+ mMetadataIndex--;
+ }
+ }
+
+ // Internal constructor for walking from one metadata to another metadata on
+ // the same WebRenderLayerScrollData.
+ WebRenderScrollDataWrapper(const APZUpdater* aUpdater,
+ const WebRenderScrollData* aData,
+ size_t aLayerIndex,
+ size_t aContainingSubtreeLastIndex,
+ const WebRenderLayerScrollData* aLayer,
+ uint32_t aMetadataIndex)
+ : mUpdater(aUpdater),
+ mData(aData),
+ mLayerIndex(aLayerIndex),
+ mContainingSubtreeLastIndex(aContainingSubtreeLastIndex),
+ mLayer(aLayer),
+ mMetadataIndex(aMetadataIndex) {
+ MOZ_ASSERT(mData);
+ MOZ_ASSERT(mLayer);
+ MOZ_ASSERT(mLayer == mData->GetLayerData(mLayerIndex));
+ MOZ_ASSERT(mMetadataIndex == 0 ||
+ mMetadataIndex < mLayer->GetScrollMetadataCount());
+ }
+
+ public:
+ bool IsValid() const { return mLayer != nullptr; }
+
+ explicit operator bool() const { return IsValid(); }
+
+ WebRenderScrollDataWrapper GetLastChild() const {
+ MOZ_ASSERT(IsValid());
+
+ if (!AtBottomLayer()) {
+ // If we're still walking around in the virtual container layers created
+ // by the ScrollMetadata array, we just need to update the metadata index
+ // and that's it.
+ return WebRenderScrollDataWrapper(mUpdater, mData, mLayerIndex,
+ mContainingSubtreeLastIndex, mLayer,
+ mMetadataIndex - 1);
+ }
+
+ // Otherwise, we need to walk to a different WebRenderLayerScrollData in
+ // mData.
+
+ // Since mData contains the layer in depth-first, last-to-first order,
+ // the index after mLayerIndex must be mLayerIndex's last child, if it
+ // has any children (indicated by GetDescendantCount() > 0). Furthermore
+ // we compute the first index outside the subtree rooted at this node
+ // (in |subtreeLastIndex|) and pass that in to the child wrapper to use as
+ // its mContainingSubtreeLastIndex.
+ if (mLayer->GetDescendantCount() > 0) {
+ size_t prevSiblingIndex = mLayerIndex + 1 + mLayer->GetDescendantCount();
+ // TODO(botond): Replace the min() with just prevSiblingIndex (which
+ // should be <= mContainingSubtreeLastIndex).
+ MOZ_ASSERT(prevSiblingIndex <= mContainingSubtreeLastIndex);
+ size_t subtreeLastIndex =
+ std::min(mContainingSubtreeLastIndex, prevSiblingIndex);
+ return WebRenderScrollDataWrapper(mUpdater, mData, mLayerIndex + 1,
+ subtreeLastIndex);
+ }
+
+ // We've run out of descendants. But! If the original layer was a RefLayer,
+ // then it connects to another layer tree and we need to traverse that too.
+ // So return a WebRenderScrollDataWrapper for the root of the child layer
+ // tree.
+ if (mLayer->GetReferentId()) {
+ return WebRenderScrollDataWrapper(
+ *mUpdater, mUpdater->GetScrollData(*mLayer->GetReferentId()));
+ }
+
+ return WebRenderScrollDataWrapper(*mUpdater);
+ }
+
+ WebRenderScrollDataWrapper GetPrevSibling() const {
+ MOZ_ASSERT(IsValid());
+
+ if (!AtTopLayer()) {
+ // The virtual container layers don't have siblings
+ return WebRenderScrollDataWrapper(*mUpdater);
+ }
+
+ // Skip past the descendants to get to the previous sibling. However, we
+ // might be at the last sibling already.
+ size_t prevSiblingIndex = mLayerIndex + 1 + mLayer->GetDescendantCount();
+ if (prevSiblingIndex < mContainingSubtreeLastIndex) {
+ return WebRenderScrollDataWrapper(mUpdater, mData, prevSiblingIndex,
+ mContainingSubtreeLastIndex);
+ }
+ return WebRenderScrollDataWrapper(*mUpdater);
+ }
+
+ const ScrollMetadata& Metadata() const {
+ MOZ_ASSERT(IsValid());
+
+ if (mMetadataIndex >= mLayer->GetScrollMetadataCount()) {
+ return *ScrollMetadata::sNullMetadata;
+ }
+ return mLayer->GetScrollMetadata(*mData, mMetadataIndex);
+ }
+
+ const FrameMetrics& Metrics() const { return Metadata().GetMetrics(); }
+
+ AsyncPanZoomController* GetApzc() const { return nullptr; }
+
+ void SetApzc(AsyncPanZoomController* aApzc) const {}
+
+ const char* Name() const { return "WebRenderScrollDataWrapper"; }
+
+ gfx::Matrix4x4 GetTransform() const {
+ MOZ_ASSERT(IsValid());
+
+ // See WebRenderLayerScrollData::Initialize for more context.
+ // * The ancestor transform is associated with whichever layer has scroll
+ // id matching GetAncestorTransformId().
+ // * The transform is associated with the "bottommost" layer.
+ // Multiple transforms may apply to the same layer (e.g. if there is only
+ // one scrollmetadata on the layer, then it is both "topmost" and
+ // "bottommost"), so we may need to combine the transforms.
+
+ gfx::Matrix4x4 transform;
+ // The ancestor transform is usually emitted at the layer with the
+ // matching scroll id. However, sometimes the transform ends up on
+ // a node with no scroll metadata at all. In such cases we generate
+ // a single layer, and the ancestor transform needs to be on that layer,
+ // otherwise it will be lost.
+ bool emitAncestorTransform =
+ !Metrics().IsScrollable() ||
+ Metrics().GetScrollId() == mLayer->GetAncestorTransformId();
+ if (emitAncestorTransform) {
+ transform = mLayer->GetAncestorTransform();
+ }
+ if (AtBottomLayer()) {
+ transform = mLayer->GetTransform() * transform;
+ }
+ return transform;
+ }
+
+ CSSTransformMatrix GetTransformTyped() const {
+ return ViewAs<CSSTransformMatrix>(GetTransform());
+ }
+
+ bool TransformIsPerspective() const {
+ MOZ_ASSERT(IsValid());
+
+ // mLayer->GetTransformIsPerspective() tells us whether
+ // mLayer->GetTransform() is a perspective transform. Since
+ // mLayer->GetTransform() is only used at the bottom layer, we only
+ // need to check GetTransformIsPerspective() at the bottom layer too.
+ if (AtBottomLayer()) {
+ return mLayer->GetTransformIsPerspective();
+ }
+ return false;
+ }
+
+ LayerIntRect GetVisibleRect() const {
+ MOZ_ASSERT(IsValid());
+
+ if (AtBottomLayer()) {
+ return mLayer->GetVisibleRect();
+ }
+
+ return ViewAs<LayerPixel>(
+ TransformBy(mLayer->GetTransformTyped(), mLayer->GetVisibleRect()),
+ PixelCastJustification::MovingDownToChildren);
+ }
+
+ LayerIntSize GetRemoteDocumentSize() const {
+ MOZ_ASSERT(IsValid());
+
+ if (mLayer->GetReferentId().isNothing()) {
+ return LayerIntSize();
+ }
+
+ if (AtBottomLayer()) {
+ return mLayer->GetRemoteDocumentSize();
+ }
+
+ return ViewAs<LayerPixel>(mLayer->GetRemoteDocumentSize(),
+ PixelCastJustification::MovingDownToChildren);
+ }
+
+ Maybe<LayersId> GetReferentId() const {
+ MOZ_ASSERT(IsValid());
+
+ if (AtBottomLayer()) {
+ return mLayer->GetReferentId();
+ }
+ return Nothing();
+ }
+
+ EventRegionsOverride GetEventRegionsOverride() const {
+ MOZ_ASSERT(IsValid());
+ // Only ref layers can have an event regions override.
+ if (GetReferentId()) {
+ return mLayer->GetEventRegionsOverride();
+ }
+ return EventRegionsOverride::NoOverride;
+ }
+
+ const ScrollbarData& GetScrollbarData() const {
+ MOZ_ASSERT(IsValid());
+ return mLayer->GetScrollbarData();
+ }
+
+ Maybe<uint64_t> GetScrollbarAnimationId() const {
+ MOZ_ASSERT(IsValid());
+ return mLayer->GetScrollbarAnimationId();
+ }
+
+ Maybe<uint64_t> GetFixedPositionAnimationId() const {
+ MOZ_ASSERT(IsValid());
+
+ if (AtBottomLayer()) {
+ return mLayer->GetFixedPositionAnimationId();
+ }
+ return Nothing();
+ }
+
+ ScrollableLayerGuid::ViewID GetFixedPositionScrollContainerId() const {
+ MOZ_ASSERT(IsValid());
+
+ if (AtBottomLayer()) {
+ return mLayer->GetFixedPositionScrollContainerId();
+ }
+ return ScrollableLayerGuid::NULL_SCROLL_ID;
+ }
+
+ SideBits GetFixedPositionSides() const {
+ MOZ_ASSERT(IsValid());
+
+ if (AtBottomLayer()) {
+ return mLayer->GetFixedPositionSides();
+ }
+ return SideBits::eNone;
+ }
+
+ ScrollableLayerGuid::ViewID GetStickyScrollContainerId() const {
+ MOZ_ASSERT(IsValid());
+
+ if (AtBottomLayer()) {
+ return mLayer->GetStickyPositionScrollContainerId();
+ }
+ return ScrollableLayerGuid::NULL_SCROLL_ID;
+ }
+
+ const LayerRectAbsolute& GetStickyScrollRangeOuter() const {
+ MOZ_ASSERT(IsValid());
+
+ if (AtBottomLayer()) {
+ return mLayer->GetStickyScrollRangeOuter();
+ }
+
+ static const LayerRectAbsolute empty;
+ return empty;
+ }
+
+ const LayerRectAbsolute& GetStickyScrollRangeInner() const {
+ MOZ_ASSERT(IsValid());
+
+ if (AtBottomLayer()) {
+ return mLayer->GetStickyScrollRangeInner();
+ }
+
+ static const LayerRectAbsolute empty;
+ return empty;
+ }
+
+ Maybe<uint64_t> GetStickyPositionAnimationId() const {
+ MOZ_ASSERT(IsValid());
+
+ if (AtBottomLayer()) {
+ return mLayer->GetStickyPositionAnimationId();
+ }
+ return Nothing();
+ }
+
+ Maybe<uint64_t> GetZoomAnimationId() const {
+ MOZ_ASSERT(IsValid());
+ return mLayer->GetZoomAnimationId();
+ }
+
+ Maybe<ScrollableLayerGuid::ViewID> GetAsyncZoomContainerId() const {
+ MOZ_ASSERT(IsValid());
+ return mLayer->GetAsyncZoomContainerId();
+ }
+
+ // Expose an opaque pointer to the layer. Mostly used for printf
+ // purposes. This is not intended to be a general-purpose accessor
+ // for the underlying layer.
+ const void* GetLayer() const {
+ MOZ_ASSERT(IsValid());
+ return mLayer;
+ }
+
+ template <int Level>
+ size_t Dump(gfx::TreeLog<Level>& aOut) const {
+ std::string result = "(invalid)";
+ if (!IsValid()) {
+ aOut << result;
+ return result.length();
+ }
+ if (AtBottomLayer()) {
+ if (mData != nullptr) {
+ const WebRenderLayerScrollData* layerData =
+ mData->GetLayerData(mLayerIndex);
+ if (layerData != nullptr) {
+ std::stringstream ss;
+ layerData->Dump(ss, *mData);
+ result = ss.str();
+ aOut << result;
+ return result.length();
+ }
+ }
+ }
+ return 0;
+ }
+
+ private:
+ bool AtBottomLayer() const { return mMetadataIndex == 0; }
+
+ bool AtTopLayer() const {
+ return mLayer->GetScrollMetadataCount() == 0 ||
+ mMetadataIndex == mLayer->GetScrollMetadataCount() - 1;
+ }
+
+ private:
+ const APZUpdater* mUpdater;
+ const WebRenderScrollData* mData;
+ // The index (in mData->mLayerScrollData) of the WebRenderLayerScrollData this
+ // wrapper is pointing to.
+ size_t mLayerIndex;
+ // The upper bound on the set of valid indices inside the subtree rooted at
+ // the parent of this "layer". That is, any layer index |i| in the range
+ // mLayerIndex <= i < mContainingSubtreeLastIndex is guaranteed to point to
+ // a layer that is a descendant of "parent", where "parent" is the parent
+ // layer of the layer at mLayerIndex. This is needed in order to implement
+ // GetPrevSibling() correctly.
+ size_t mContainingSubtreeLastIndex;
+ // The WebRenderLayerScrollData this wrapper is pointing to.
+ const WebRenderLayerScrollData* mLayer;
+ // The index of the scroll metadata within mLayer that this wrapper is
+ // pointing to.
+ uint32_t mMetadataIndex;
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif /* GFX_WEBRENDERSCROLLDATAWRAPPER_H */
diff --git a/gfx/layers/wr/WebRenderTextureHost.cpp b/gfx/layers/wr/WebRenderTextureHost.cpp
new file mode 100644
index 0000000000..707588a1f3
--- /dev/null
+++ b/gfx/layers/wr/WebRenderTextureHost.cpp
@@ -0,0 +1,219 @@
+/* -*- 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 "WebRenderTextureHost.h"
+
+#include "mozilla/layers/ImageDataSerializer.h"
+#include "mozilla/layers/LayersSurfaces.h"
+#include "mozilla/layers/TextureSourceProvider.h"
+#include "mozilla/webrender/RenderThread.h"
+#include "mozilla/webrender/WebRenderAPI.h"
+
+#ifdef MOZ_WIDGET_ANDROID
+# include "mozilla/layers/TextureHostOGL.h"
+#endif
+
+namespace mozilla::layers {
+
+class ScheduleHandleRenderTextureOps : public wr::NotificationHandler {
+ public:
+ explicit ScheduleHandleRenderTextureOps() {}
+
+ virtual void Notify(wr::Checkpoint aCheckpoint) override {
+ if (aCheckpoint == wr::Checkpoint::FrameTexturesUpdated) {
+ MOZ_ASSERT(wr::RenderThread::IsInRenderThread());
+ wr::RenderThread::Get()->HandleRenderTextureOps();
+ } else {
+ MOZ_ASSERT(aCheckpoint == wr::Checkpoint::TransactionDropped);
+ }
+ }
+
+ protected:
+};
+
+WebRenderTextureHost::WebRenderTextureHost(
+ TextureFlags aFlags, TextureHost* aTexture,
+ const wr::ExternalImageId& aExternalImageId)
+ : TextureHost(TextureHostType::Unknown, aFlags),
+ mWrappedTextureHost(aTexture) {
+ MOZ_ASSERT(mWrappedTextureHost);
+ // The wrapped textureHost will be used in WebRender, and the WebRender could
+ // run at another thread. It's hard to control the life-time when gecko
+ // receives PTextureParent destroy message. It's possible that textureHost is
+ // still used by WebRender. So, we only accept the textureHost without
+ // DEALLOCATE_CLIENT flag here. If the buffer deallocation is controlled by
+ // parent, we could do something to make sure the wrapped textureHost is not
+ // used by WebRender and then release it.
+ MOZ_ASSERT(!(aFlags & TextureFlags::DEALLOCATE_CLIENT));
+ MOZ_COUNT_CTOR(WebRenderTextureHost);
+
+ mExternalImageId = Some(aExternalImageId);
+}
+
+WebRenderTextureHost::~WebRenderTextureHost() {
+ MOZ_COUNT_DTOR(WebRenderTextureHost);
+}
+
+wr::ExternalImageId WebRenderTextureHost::GetExternalImageKey() {
+ if (IsValid()) {
+ mWrappedTextureHost->EnsureRenderTexture(mExternalImageId);
+ }
+ MOZ_ASSERT(mWrappedTextureHost->mExternalImageId.isSome());
+ return mWrappedTextureHost->mExternalImageId.ref();
+}
+
+bool WebRenderTextureHost::IsValid() { return mWrappedTextureHost->IsValid(); }
+
+void WebRenderTextureHost::UnbindTextureSource() {
+ if (mWrappedTextureHost->AsBufferTextureHost()) {
+ mWrappedTextureHost->UnbindTextureSource();
+ }
+ // Handle read unlock
+ TextureHost::UnbindTextureSource();
+}
+
+already_AddRefed<gfx::DataSourceSurface> WebRenderTextureHost::GetAsSurface() {
+ return mWrappedTextureHost->GetAsSurface();
+}
+
+gfx::ColorDepth WebRenderTextureHost::GetColorDepth() const {
+ return mWrappedTextureHost->GetColorDepth();
+}
+
+gfx::YUVColorSpace WebRenderTextureHost::GetYUVColorSpace() const {
+ return mWrappedTextureHost->GetYUVColorSpace();
+}
+
+gfx::ColorRange WebRenderTextureHost::GetColorRange() const {
+ return mWrappedTextureHost->GetColorRange();
+}
+
+gfx::IntSize WebRenderTextureHost::GetSize() const {
+ return mWrappedTextureHost->GetSize();
+}
+
+gfx::SurfaceFormat WebRenderTextureHost::GetFormat() const {
+ return mWrappedTextureHost->GetFormat();
+}
+
+void WebRenderTextureHost::MaybeDestroyRenderTexture() {
+ // WebRenderTextureHost does not create RenderTexture, then
+ // WebRenderTextureHost does not need to destroy RenderTexture.
+ mExternalImageId = Nothing();
+}
+
+void WebRenderTextureHost::NotifyNotUsed() {
+#ifdef MOZ_WIDGET_ANDROID
+ // When SurfaceTextureHost is wrapped by RemoteTextureHostWrapper,
+ // NotifyNotUsed() is handled by SurfaceTextureHost.
+ if (IsWrappingSurfaceTextureHost() &&
+ !mWrappedTextureHost->AsRemoteTextureHostWrapper()) {
+ wr::RenderThread::Get()->NotifyNotUsed(GetExternalImageKey());
+ }
+#endif
+ if (mWrappedTextureHost->AsRemoteTextureHostWrapper() ||
+ mWrappedTextureHost->AsTextureHostWrapperD3D11()) {
+ mWrappedTextureHost->NotifyNotUsed();
+ }
+ TextureHost::NotifyNotUsed();
+}
+
+void WebRenderTextureHost::MaybeNotifyForUse(wr::TransactionBuilder& aTxn) {
+#if defined(MOZ_WIDGET_ANDROID)
+ if (IsWrappingSurfaceTextureHost() &&
+ !mWrappedTextureHost->AsRemoteTextureHostWrapper()) {
+ wr::RenderThread::Get()->NotifyForUse(GetExternalImageKey());
+ aTxn.Notify(wr::Checkpoint::FrameTexturesUpdated,
+ MakeUnique<ScheduleHandleRenderTextureOps>());
+ }
+#endif
+}
+
+bool WebRenderTextureHost::IsWrappingSurfaceTextureHost() {
+ return mWrappedTextureHost->IsWrappingSurfaceTextureHost();
+}
+
+void WebRenderTextureHost::PrepareForUse() {
+ // When SurfaceTextureHost is wrapped by RemoteTextureHostWrapper,
+ // PrepareForUse() is handled by SurfaceTextureHost.
+ if ((IsWrappingSurfaceTextureHost() &&
+ !mWrappedTextureHost->AsRemoteTextureHostWrapper()) ||
+ mWrappedTextureHost->AsBufferTextureHost()) {
+ // Call PrepareForUse on render thread.
+ // See RenderAndroidSurfaceTextureHostOGL::PrepareForUse.
+ wr::RenderThread::Get()->PrepareForUse(GetExternalImageKey());
+ }
+}
+
+gfx::SurfaceFormat WebRenderTextureHost::GetReadFormat() const {
+ return mWrappedTextureHost->GetReadFormat();
+}
+
+int32_t WebRenderTextureHost::GetRGBStride() {
+ gfx::SurfaceFormat format = GetFormat();
+ if (GetFormat() == gfx::SurfaceFormat::YUV) {
+ // XXX this stride is used until yuv image rendering by webrender is used.
+ // Software converted RGB buffers strides are aliened to 16
+ return gfx::GetAlignedStride<16>(
+ GetSize().width, BytesPerPixel(gfx::SurfaceFormat::B8G8R8A8));
+ }
+ return ImageDataSerializer::ComputeRGBStride(format, GetSize().width);
+}
+
+bool WebRenderTextureHost::NeedsDeferredDeletion() const {
+ return mWrappedTextureHost->NeedsDeferredDeletion();
+}
+
+uint32_t WebRenderTextureHost::NumSubTextures() {
+ return mWrappedTextureHost->NumSubTextures();
+}
+
+void WebRenderTextureHost::PushResourceUpdates(
+ wr::TransactionBuilder& aResources, ResourceUpdateOp aOp,
+ const Range<wr::ImageKey>& aImageKeys, const wr::ExternalImageId& aExtID) {
+ MOZ_ASSERT(GetExternalImageKey() == aExtID);
+
+ mWrappedTextureHost->PushResourceUpdates(aResources, aOp, aImageKeys, aExtID);
+}
+
+void WebRenderTextureHost::PushDisplayItems(
+ wr::DisplayListBuilder& aBuilder, const wr::LayoutRect& aBounds,
+ const wr::LayoutRect& aClip, wr::ImageRendering aFilter,
+ const Range<wr::ImageKey>& aImageKeys, PushDisplayItemFlagSet aFlags) {
+ MOZ_ASSERT(aImageKeys.length() > 0);
+
+ mWrappedTextureHost->PushDisplayItems(aBuilder, aBounds, aClip, aFilter,
+ aImageKeys, aFlags);
+}
+
+bool WebRenderTextureHost::SupportsExternalCompositing(
+ WebRenderBackend aBackend) {
+ return mWrappedTextureHost->SupportsExternalCompositing(aBackend);
+}
+
+void WebRenderTextureHost::SetAcquireFence(
+ mozilla::ipc::FileDescriptor&& aFenceFd) {
+ mWrappedTextureHost->SetAcquireFence(std::move(aFenceFd));
+}
+
+void WebRenderTextureHost::SetReleaseFence(
+ mozilla::ipc::FileDescriptor&& aFenceFd) {
+ mWrappedTextureHost->SetReleaseFence(std::move(aFenceFd));
+}
+
+mozilla::ipc::FileDescriptor WebRenderTextureHost::GetAndResetReleaseFence() {
+ return mWrappedTextureHost->GetAndResetReleaseFence();
+}
+
+AndroidHardwareBuffer* WebRenderTextureHost::GetAndroidHardwareBuffer() const {
+ return mWrappedTextureHost->GetAndroidHardwareBuffer();
+}
+
+TextureHostType WebRenderTextureHost::GetTextureHostType() {
+ return mWrappedTextureHost->GetTextureHostType();
+}
+
+} // namespace mozilla::layers
diff --git a/gfx/layers/wr/WebRenderTextureHost.h b/gfx/layers/wr/WebRenderTextureHost.h
new file mode 100644
index 0000000000..21e5e4780e
--- /dev/null
+++ b/gfx/layers/wr/WebRenderTextureHost.h
@@ -0,0 +1,113 @@
+/* -*- 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/. */
+
+#ifndef MOZILLA_GFX_WEBRENDERTEXTUREHOST_H
+#define MOZILLA_GFX_WEBRENDERTEXTUREHOST_H
+
+#include "mozilla/layers/TextureHost.h"
+#include "mozilla/webrender/WebRenderTypes.h"
+
+namespace mozilla {
+namespace layers {
+
+class SurfaceDescriptor;
+
+// This textureHost is specialized for WebRender usage. With WebRender, there is
+// no Compositor during composition. Instead, we use RendererOGL for
+// composition. So, there are some UNREACHABLE asserts for the original
+// Compositor related code path in this class. Furthermore, the RendererOGL runs
+// at RenderThead instead of Compositor thread. This class is also creating the
+// corresponding RenderXXXTextureHost used by RendererOGL at RenderThread.
+class WebRenderTextureHost : public TextureHost {
+ public:
+ WebRenderTextureHost(TextureFlags aFlags, TextureHost* aTexture,
+ const wr::ExternalImageId& aExternalImageId);
+ virtual ~WebRenderTextureHost();
+
+ void DeallocateDeviceData() override {}
+
+ void UnbindTextureSource() override;
+
+ gfx::SurfaceFormat GetFormat() const override;
+
+ virtual void NotifyNotUsed() override;
+
+ virtual bool IsValid() override;
+
+ // Return the format used for reading the texture. Some hardware specific
+ // textureHosts use their special data representation internally, but we could
+ // treat these textureHost as the read-format when we read them.
+ // Please check TextureHost::GetReadFormat().
+ gfx::SurfaceFormat GetReadFormat() const override;
+
+ already_AddRefed<gfx::DataSourceSurface> GetAsSurface() override;
+
+ gfx::ColorDepth GetColorDepth() const override;
+ gfx::YUVColorSpace GetYUVColorSpace() const override;
+ gfx::ColorRange GetColorRange() const override;
+
+ gfx::IntSize GetSize() const override;
+
+#ifdef MOZ_LAYERS_HAVE_LOG
+ const char* Name() override { return "WebRenderTextureHost"; }
+#endif
+
+ void MaybeDestroyRenderTexture() override;
+
+ WebRenderTextureHost* AsWebRenderTextureHost() override { return this; }
+
+ RemoteTextureHostWrapper* AsRemoteTextureHostWrapper() override {
+ return mWrappedTextureHost->AsRemoteTextureHostWrapper();
+ }
+
+ BufferTextureHost* AsBufferTextureHost() override {
+ return mWrappedTextureHost->AsBufferTextureHost();
+ }
+
+ bool IsWrappingSurfaceTextureHost() override;
+
+ virtual void PrepareForUse() override;
+
+ wr::ExternalImageId GetExternalImageKey();
+
+ int32_t GetRGBStride();
+
+ bool NeedsDeferredDeletion() const override;
+
+ uint32_t NumSubTextures() override;
+
+ void PushResourceUpdates(wr::TransactionBuilder& aResources,
+ ResourceUpdateOp aOp,
+ const Range<wr::ImageKey>& aImageKeys,
+ const wr::ExternalImageId& aExtID) override;
+
+ void PushDisplayItems(wr::DisplayListBuilder& aBuilder,
+ const wr::LayoutRect& aBounds,
+ const wr::LayoutRect& aClip, wr::ImageRendering aFilter,
+ const Range<wr::ImageKey>& aImageKeys,
+ PushDisplayItemFlagSet aFlags) override;
+
+ bool SupportsExternalCompositing(WebRenderBackend aBackend) override;
+
+ void SetAcquireFence(mozilla::ipc::FileDescriptor&& aFenceFd) override;
+
+ void SetReleaseFence(mozilla::ipc::FileDescriptor&& aFenceFd) override;
+
+ mozilla::ipc::FileDescriptor GetAndResetReleaseFence() override;
+
+ AndroidHardwareBuffer* GetAndroidHardwareBuffer() const override;
+
+ void MaybeNotifyForUse(wr::TransactionBuilder& aTxn);
+
+ TextureHostType GetTextureHostType() override;
+
+ const RefPtr<TextureHost> mWrappedTextureHost;
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif // MOZILLA_GFX_WEBRENDERTEXTUREHOST_H
diff --git a/gfx/layers/wr/WebRenderUserData.cpp b/gfx/layers/wr/WebRenderUserData.cpp
new file mode 100644
index 0000000000..cc8c18b7f6
--- /dev/null
+++ b/gfx/layers/wr/WebRenderUserData.cpp
@@ -0,0 +1,427 @@
+/* -*- 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 "WebRenderUserData.h"
+
+#include "mozilla/image/WebRenderImageProvider.h"
+#include "mozilla/layers/AnimationHelper.h"
+#include "mozilla/layers/CompositorBridgeChild.h"
+#include "mozilla/layers/ImageClient.h"
+#include "mozilla/layers/WebRenderBridgeChild.h"
+#include "mozilla/layers/RenderRootStateManager.h"
+#include "mozilla/layers/WebRenderMessages.h"
+#include "mozilla/layers/IpcResourceUpdateQueue.h"
+#include "mozilla/layers/SharedSurfacesChild.h"
+#include "mozilla/webgpu/WebGPUChild.h"
+#include "nsDisplayListInvalidation.h"
+#include "nsIFrame.h"
+#include "WebRenderCanvasRenderer.h"
+
+using namespace mozilla::image;
+
+namespace mozilla {
+namespace layers {
+
+void WebRenderBackgroundData::AddWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder) {
+ aBuilder.PushRect(mBounds, mBounds, true, true, false, mColor);
+}
+
+/* static */
+bool WebRenderUserData::SupportsAsyncUpdate(nsIFrame* aFrame) {
+ if (!aFrame) {
+ return false;
+ }
+ RefPtr<WebRenderImageData> data = GetWebRenderUserData<WebRenderImageData>(
+ aFrame, static_cast<uint32_t>(DisplayItemType::TYPE_VIDEO));
+ if (data) {
+ return data->IsAsync();
+ }
+
+ return false;
+}
+
+/* static */
+bool WebRenderUserData::ProcessInvalidateForImage(nsIFrame* aFrame,
+ DisplayItemType aType,
+ ImageProviderId aProviderId) {
+ MOZ_ASSERT(aFrame);
+
+ if (!aFrame->HasProperty(WebRenderUserDataProperty::Key())) {
+ aFrame->SchedulePaint();
+ return false;
+ }
+
+ auto type = static_cast<uint32_t>(aType);
+ RefPtr<WebRenderFallbackData> fallback =
+ GetWebRenderUserData<WebRenderFallbackData>(aFrame, type);
+ if (fallback) {
+ fallback->SetInvalid(true);
+ aFrame->SchedulePaint();
+ return true;
+ }
+
+ RefPtr<WebRenderImageProviderData> image =
+ GetWebRenderUserData<WebRenderImageProviderData>(aFrame, type);
+ if (image && image->Invalidate(aProviderId)) {
+ return true;
+ }
+
+ aFrame->SchedulePaint();
+ return false;
+}
+
+WebRenderUserData::WebRenderUserData(RenderRootStateManager* aManager,
+ uint32_t aDisplayItemKey, nsIFrame* aFrame)
+ : mManager(aManager),
+ mFrame(aFrame),
+ mDisplayItemKey(aDisplayItemKey),
+ mTable(aManager->GetWebRenderUserDataTable()),
+ mUsed(false) {}
+
+WebRenderUserData::WebRenderUserData(RenderRootStateManager* aManager,
+ nsDisplayItem* aItem)
+ : mManager(aManager),
+ mFrame(aItem->Frame()),
+ mDisplayItemKey(aItem->GetPerFrameKey()),
+ mTable(aManager->GetWebRenderUserDataTable()),
+ mUsed(false) {}
+
+WebRenderUserData::~WebRenderUserData() = default;
+
+void WebRenderUserData::RemoveFromTable() { mTable->Remove(this); }
+
+WebRenderBridgeChild* WebRenderUserData::WrBridge() const {
+ return mManager->WrBridge();
+}
+
+WebRenderImageData::WebRenderImageData(RenderRootStateManager* aManager,
+ nsDisplayItem* aItem)
+ : WebRenderUserData(aManager, aItem) {}
+
+WebRenderImageData::WebRenderImageData(RenderRootStateManager* aManager,
+ uint32_t aDisplayItemKey,
+ nsIFrame* aFrame)
+ : WebRenderUserData(aManager, aDisplayItemKey, aFrame) {}
+
+WebRenderImageData::~WebRenderImageData() {
+ ClearImageKey();
+
+ if (mPipelineId) {
+ mManager->RemovePipelineIdForCompositable(mPipelineId.ref());
+ }
+}
+
+void WebRenderImageData::ClearImageKey() {
+ if (mKey) {
+ mManager->AddImageKeyForDiscard(mKey.value());
+ if (mTextureOfImage) {
+ WrBridge()->ReleaseTextureOfImage(mKey.value());
+ mTextureOfImage = nullptr;
+ }
+ mKey.reset();
+ }
+ MOZ_ASSERT(!mTextureOfImage);
+}
+
+Maybe<wr::ImageKey> WebRenderImageData::UpdateImageKey(
+ ImageContainer* aContainer, wr::IpcResourceUpdateQueue& aResources,
+ bool aFallback) {
+ MOZ_ASSERT(aContainer);
+
+ if (mContainer != aContainer) {
+ mContainer = aContainer;
+ }
+
+ CreateImageClientIfNeeded();
+ if (!mImageClient) {
+ return Nothing();
+ }
+
+ MOZ_ASSERT(mImageClient->AsImageClientSingle());
+
+ ImageClientSingle* imageClient = mImageClient->AsImageClientSingle();
+ uint32_t oldCounter = imageClient->GetLastUpdateGenerationCounter();
+
+ bool ret = imageClient->UpdateImage(aContainer);
+ RefPtr<TextureClient> currentTexture = imageClient->GetForwardedTexture();
+ if (!ret || !currentTexture) {
+ // Delete old key
+ ClearImageKey();
+ return Nothing();
+ }
+
+ // Reuse old key if generation is not updated.
+ if (!aFallback &&
+ oldCounter == imageClient->GetLastUpdateGenerationCounter() && mKey) {
+ return mKey;
+ }
+
+ // If we already had a texture and the format hasn't changed, better to reuse
+ // the image keys than create new ones.
+ bool useUpdate = mKey.isSome() && !!mTextureOfImage && !!currentTexture &&
+ mTextureOfImage->GetSize() == currentTexture->GetSize() &&
+ mTextureOfImage->GetFormat() == currentTexture->GetFormat();
+
+ wr::MaybeExternalImageId extId = currentTexture->GetExternalImageKey();
+ MOZ_RELEASE_ASSERT(extId.isSome());
+
+ if (useUpdate) {
+ MOZ_ASSERT(mKey.isSome());
+ MOZ_ASSERT(mTextureOfImage);
+ aResources.PushExternalImageForTexture(
+ extId.ref(), mKey.ref(), currentTexture, /* aIsUpdate */ true);
+ } else {
+ ClearImageKey();
+ wr::WrImageKey key = WrBridge()->GetNextImageKey();
+ aResources.PushExternalImageForTexture(extId.ref(), key, currentTexture,
+ /* aIsUpdate */ false);
+ mKey = Some(key);
+ }
+
+ mTextureOfImage = currentTexture;
+ return mKey;
+}
+
+already_AddRefed<ImageClient> WebRenderImageData::GetImageClient() {
+ RefPtr<ImageClient> imageClient = mImageClient;
+ return imageClient.forget();
+}
+
+void WebRenderImageData::CreateAsyncImageWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder, ImageContainer* aContainer,
+ const StackingContextHelper& aSc, const LayoutDeviceRect& aBounds,
+ const LayoutDeviceRect& aSCBounds, wr::WrRotation aRotation,
+ const wr::ImageRendering& aFilter, const wr::MixBlendMode& aMixBlendMode,
+ bool aIsBackfaceVisible) {
+ MOZ_ASSERT(aContainer->IsAsync());
+
+ if (mPipelineId.isSome() && mContainer != aContainer) {
+ // In this case, we need to remove the existed pipeline and create new one
+ // because the ImageContainer is changed.
+ WrBridge()->RemovePipelineIdForCompositable(mPipelineId.ref());
+ mPipelineId.reset();
+ }
+
+ if (!mPipelineId) {
+ // Alloc async image pipeline id.
+ mPipelineId =
+ Some(WrBridge()->GetCompositorBridgeChild()->GetNextPipelineId());
+ WrBridge()->AddPipelineIdForCompositable(
+ mPipelineId.ref(), aContainer->GetAsyncContainerHandle(),
+ CompositableHandleOwner::ImageBridge);
+ mContainer = aContainer;
+ }
+ MOZ_ASSERT(!mImageClient);
+
+ // Push IFrame for async image pipeline.
+ //
+ // We don't push a stacking context for this async image pipeline here.
+ // Instead, we do it inside the iframe that hosts the image. As a result,
+ // a bunch of the calculations normally done as part of that stacking
+ // context need to be done manually and pushed over to the parent side,
+ // where it will be done when we build the display list for the iframe.
+ // That happens in AsyncImagePipelineManager.
+ aBuilder.PushIFrame(aBounds, aIsBackfaceVisible, mPipelineId.ref(),
+ /*ignoreMissingPipelines*/ false);
+
+ WrBridge()->AddWebRenderParentCommand(OpUpdateAsyncImagePipeline(
+ mPipelineId.value(), aSCBounds, aRotation, aFilter, aMixBlendMode));
+}
+
+void WebRenderImageData::CreateImageClientIfNeeded() {
+ if (!mImageClient) {
+ mImageClient = ImageClient::CreateImageClient(
+ CompositableType::IMAGE, WrBridge(), TextureFlags::DEFAULT);
+ if (!mImageClient) {
+ return;
+ }
+
+ mImageClient->Connect();
+ }
+}
+
+WebRenderImageProviderData::WebRenderImageProviderData(
+ RenderRootStateManager* aManager, nsDisplayItem* aItem)
+ : WebRenderUserData(aManager, aItem) {}
+
+WebRenderImageProviderData::WebRenderImageProviderData(
+ RenderRootStateManager* aManager, uint32_t aDisplayItemKey,
+ nsIFrame* aFrame)
+ : WebRenderUserData(aManager, aDisplayItemKey, aFrame) {}
+
+WebRenderImageProviderData::~WebRenderImageProviderData() = default;
+
+Maybe<wr::ImageKey> WebRenderImageProviderData::UpdateImageKey(
+ WebRenderImageProvider* aProvider, ImgDrawResult aDrawResult,
+ wr::IpcResourceUpdateQueue& aResources) {
+ if (mProvider != aProvider) {
+ mProvider = aProvider;
+ }
+
+ wr::ImageKey key = {};
+ nsresult rv = mProvider ? mProvider->UpdateKey(mManager, aResources, key)
+ : NS_ERROR_FAILURE;
+ mKey = NS_SUCCEEDED(rv) ? Some(key) : Nothing();
+ mDrawResult = aDrawResult;
+ return mKey;
+}
+
+bool WebRenderImageProviderData::Invalidate(ImageProviderId aProviderId) const {
+ if (!aProviderId || !mProvider || mProvider->GetProviderId() != aProviderId ||
+ !mKey) {
+ return false;
+ }
+
+ if (mDrawResult != ImgDrawResult::SUCCESS &&
+ mDrawResult != ImgDrawResult::BAD_IMAGE) {
+ return false;
+ }
+
+ wr::ImageKey key = {};
+ nsresult rv =
+ mProvider->UpdateKey(mManager, mManager->AsyncResourceUpdates(), key);
+ return NS_SUCCEEDED(rv) && mKey.ref() == key;
+}
+
+WebRenderFallbackData::WebRenderFallbackData(RenderRootStateManager* aManager,
+ nsDisplayItem* aItem)
+ : WebRenderUserData(aManager, aItem), mOpacity(1.0f), mInvalid(false) {}
+
+WebRenderFallbackData::~WebRenderFallbackData() { ClearImageKey(); }
+
+void WebRenderFallbackData::SetBlobImageKey(const wr::BlobImageKey& aKey) {
+ ClearImageKey();
+ mBlobKey = Some(aKey);
+}
+
+Maybe<wr::ImageKey> WebRenderFallbackData::GetImageKey() {
+ if (mBlobKey) {
+ return Some(wr::AsImageKey(mBlobKey.value()));
+ }
+
+ if (mImageData) {
+ return mImageData->GetImageKey();
+ }
+
+ return Nothing();
+}
+
+void WebRenderFallbackData::ClearImageKey() {
+ if (mImageData) {
+ mImageData->ClearImageKey();
+ mImageData = nullptr;
+ }
+
+ if (mBlobKey) {
+ mManager->AddBlobImageKeyForDiscard(mBlobKey.value());
+ mBlobKey.reset();
+ }
+}
+
+WebRenderImageData* WebRenderFallbackData::PaintIntoImage() {
+ if (mBlobKey) {
+ mManager->AddBlobImageKeyForDiscard(mBlobKey.value());
+ mBlobKey.reset();
+ }
+
+ if (mImageData) {
+ return mImageData.get();
+ }
+
+ mImageData = MakeAndAddRef<WebRenderImageData>(mManager.get(),
+ mDisplayItemKey, mFrame);
+
+ return mImageData.get();
+}
+
+WebRenderAPZAnimationData::WebRenderAPZAnimationData(
+ RenderRootStateManager* aManager, nsDisplayItem* aItem)
+ : WebRenderUserData(aManager, aItem),
+ mAnimationId(AnimationHelper::GetNextCompositorAnimationsId()) {}
+
+WebRenderAnimationData::WebRenderAnimationData(RenderRootStateManager* aManager,
+ nsDisplayItem* aItem)
+ : WebRenderUserData(aManager, aItem) {}
+
+WebRenderAnimationData::~WebRenderAnimationData() {
+ // It may be the case that nsDisplayItem that created this WebRenderUserData
+ // gets destroyed without getting a chance to discard the compositor animation
+ // id, so we should do it as part of cleanup here.
+ uint64_t animationId = mAnimationInfo.GetCompositorAnimationsId();
+ // animationId might be 0 if mAnimationInfo never held any active animations.
+ if (animationId) {
+ mManager->AddCompositorAnimationsIdForDiscard(animationId);
+ }
+}
+
+WebRenderCanvasData::WebRenderCanvasData(RenderRootStateManager* aManager,
+ nsDisplayItem* aItem)
+ : WebRenderUserData(aManager, aItem) {}
+
+WebRenderCanvasData::~WebRenderCanvasData() {
+ if (mCanvasRenderer) {
+ mCanvasRenderer->ClearCachedResources();
+ }
+}
+
+void WebRenderCanvasData::ClearCanvasRenderer() { mCanvasRenderer = nullptr; }
+
+WebRenderCanvasRendererAsync* WebRenderCanvasData::GetCanvasRenderer() {
+ return mCanvasRenderer.get();
+}
+
+WebRenderCanvasRendererAsync* WebRenderCanvasData::CreateCanvasRenderer() {
+ mCanvasRenderer = new WebRenderCanvasRendererAsync(mManager);
+ return mCanvasRenderer.get();
+}
+
+bool WebRenderCanvasData::SetCanvasRenderer(CanvasRenderer* aCanvasRenderer) {
+ if (!aCanvasRenderer || !aCanvasRenderer->AsWebRenderCanvasRendererAsync()) {
+ return false;
+ }
+
+ auto* renderer = aCanvasRenderer->AsWebRenderCanvasRendererAsync();
+ if (mManager != renderer->GetRenderRootStateManager()) {
+ return false;
+ }
+
+ mCanvasRenderer = renderer;
+ return true;
+}
+
+void WebRenderCanvasData::SetImageContainer(ImageContainer* aImageContainer) {
+ mContainer = aImageContainer;
+}
+
+ImageContainer* WebRenderCanvasData::GetImageContainer() {
+ if (!mContainer) {
+ mContainer = MakeAndAddRef<ImageContainer>();
+ }
+ return mContainer;
+}
+
+void WebRenderCanvasData::ClearImageContainer() { mContainer = nullptr; }
+
+WebRenderRemoteData::WebRenderRemoteData(RenderRootStateManager* aManager,
+ nsDisplayItem* aItem)
+ : WebRenderUserData(aManager, aItem) {}
+
+WebRenderRemoteData::~WebRenderRemoteData() {
+ if (mRemoteBrowser) {
+ mRemoteBrowser->UpdateEffects(mozilla::dom::EffectsInfo::FullyHidden());
+ }
+}
+
+void DestroyWebRenderUserDataTable(WebRenderUserDataTable* aTable) {
+ for (const auto& value : aTable->Values()) {
+ value->RemoveFromTable();
+ }
+ delete aTable;
+}
+
+} // namespace layers
+} // namespace mozilla
diff --git a/gfx/layers/wr/WebRenderUserData.h b/gfx/layers/wr/WebRenderUserData.h
new file mode 100644
index 0000000000..49ec79c1e3
--- /dev/null
+++ b/gfx/layers/wr/WebRenderUserData.h
@@ -0,0 +1,386 @@
+/* -*- 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/. */
+
+#ifndef GFX_WEBRENDERUSERDATA_H
+#define GFX_WEBRENDERUSERDATA_H
+
+#include <vector>
+#include "mozilla/gfx/DrawEventRecorder.h"
+#include "mozilla/webrender/WebRenderAPI.h"
+#include "mozilla/image/WebRenderImageProvider.h"
+#include "mozilla/layers/AnimationInfo.h"
+#include "mozilla/layers/LayersTypes.h"
+#include "mozilla/dom/RemoteBrowser.h"
+#include "mozilla/UniquePtr.h"
+#include "nsIFrame.h"
+#include "nsRefPtrHashtable.h"
+#include "nsTHashSet.h"
+#include "ImageTypes.h"
+#include "ImgDrawResult.h"
+#include "DisplayItemClip.h"
+
+namespace mozilla {
+
+class nsDisplayItemGeometry;
+
+namespace webgpu {
+class WebGPUChild;
+}
+
+namespace wr {
+class IpcResourceUpdateQueue;
+}
+
+namespace gfx {
+class SourceSurface;
+}
+
+namespace layers {
+
+class BasicLayerManager;
+class CanvasRenderer;
+class ImageClient;
+class ImageContainer;
+class WebRenderBridgeChild;
+class WebRenderCanvasData;
+class WebRenderCanvasRenderer;
+class WebRenderCanvasRendererAsync;
+class WebRenderImageData;
+class WebRenderImageProviderData;
+class WebRenderFallbackData;
+class RenderRootStateManager;
+class WebRenderGroupData;
+
+class WebRenderBackgroundData {
+ public:
+ WebRenderBackgroundData(wr::LayoutRect aBounds, wr::ColorF aColor)
+ : mBounds(aBounds), mColor(aColor) {}
+ void AddWebRenderCommands(wr::DisplayListBuilder& aBuilder);
+
+ protected:
+ wr::LayoutRect mBounds;
+ wr::ColorF mColor;
+};
+
+/// Parent class for arbitrary WebRender-specific data that can be associated
+/// to an nsFrame.
+class WebRenderUserData {
+ public:
+ typedef nsTHashSet<RefPtr<WebRenderUserData>> WebRenderUserDataRefTable;
+
+ static bool SupportsAsyncUpdate(nsIFrame* aFrame);
+
+ static bool ProcessInvalidateForImage(nsIFrame* aFrame, DisplayItemType aType,
+ image::ImageProviderId aProviderId);
+
+ NS_INLINE_DECL_REFCOUNTING(WebRenderUserData)
+
+ WebRenderUserData(RenderRootStateManager* aManager, nsDisplayItem* aItem);
+ WebRenderUserData(RenderRootStateManager* aManager, uint32_t mDisplayItemKey,
+ nsIFrame* aFrame);
+
+ virtual WebRenderImageData* AsImageData() { return nullptr; }
+ virtual WebRenderImageProviderData* AsImageProviderData() { return nullptr; }
+ virtual WebRenderFallbackData* AsFallbackData() { return nullptr; }
+ virtual WebRenderCanvasData* AsCanvasData() { return nullptr; }
+ virtual WebRenderGroupData* AsGroupData() { return nullptr; }
+
+ enum class UserDataType {
+ eImage,
+ eFallback,
+ eAPZAnimation,
+ eAnimation,
+ eCanvas,
+ eRemote,
+ eGroup,
+ eMask,
+ eImageProvider, // ImageLib
+ eInProcessImage,
+ };
+
+ virtual UserDataType GetType() = 0;
+ bool IsUsed() { return mUsed; }
+ void SetUsed(bool aUsed) { mUsed = aUsed; }
+ nsIFrame* GetFrame() { return mFrame; }
+ uint32_t GetDisplayItemKey() { return mDisplayItemKey; }
+ void RemoveFromTable();
+ virtual nsDisplayItemGeometry* GetGeometry() { return nullptr; }
+
+ protected:
+ virtual ~WebRenderUserData();
+
+ WebRenderBridgeChild* WrBridge() const;
+
+ RefPtr<RenderRootStateManager> mManager;
+ nsIFrame* mFrame;
+ uint32_t mDisplayItemKey;
+ WebRenderUserDataRefTable* mTable;
+ bool mUsed;
+};
+
+struct WebRenderUserDataKey {
+ WebRenderUserDataKey(uint32_t aFrameKey,
+ WebRenderUserData::UserDataType aType)
+ : mFrameKey(aFrameKey), mType(aType) {}
+
+ bool operator==(const WebRenderUserDataKey& other) const {
+ return mFrameKey == other.mFrameKey && mType == other.mType;
+ }
+ PLDHashNumber Hash() const {
+ return HashGeneric(
+ mFrameKey,
+ static_cast<std::underlying_type<decltype(mType)>::type>(mType));
+ }
+
+ uint32_t mFrameKey;
+ WebRenderUserData::UserDataType mType;
+};
+
+typedef nsRefPtrHashtable<
+ nsGenericHashKey<mozilla::layers::WebRenderUserDataKey>, WebRenderUserData>
+ WebRenderUserDataTable;
+
+/// Holds some data used to share TextureClient/ImageClient with the parent
+/// process.
+class WebRenderImageData : public WebRenderUserData {
+ public:
+ WebRenderImageData(RenderRootStateManager* aManager, nsDisplayItem* aItem);
+ WebRenderImageData(RenderRootStateManager* aManager, uint32_t aDisplayItemKey,
+ nsIFrame* aFrame);
+ virtual ~WebRenderImageData();
+
+ WebRenderImageData* AsImageData() override { return this; }
+ UserDataType GetType() override { return UserDataType::eImage; }
+ static UserDataType Type() { return UserDataType::eImage; }
+ Maybe<wr::ImageKey> GetImageKey() { return mKey; }
+ void SetImageKey(const wr::ImageKey& aKey);
+ already_AddRefed<ImageClient> GetImageClient();
+
+ Maybe<wr::ImageKey> UpdateImageKey(ImageContainer* aContainer,
+ wr::IpcResourceUpdateQueue& aResources,
+ bool aFallback = false);
+
+ void CreateAsyncImageWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder, ImageContainer* aContainer,
+ const StackingContextHelper& aSc, const LayoutDeviceRect& aBounds,
+ const LayoutDeviceRect& aSCBounds, wr::WrRotation aRotation,
+ const wr::ImageRendering& aFilter, const wr::MixBlendMode& aMixBlendMode,
+ bool aIsBackfaceVisible);
+
+ void CreateImageClientIfNeeded();
+
+ bool IsAsync() { return mPipelineId.isSome(); }
+
+ void ClearImageKey();
+
+ protected:
+ Maybe<wr::ImageKey> mKey;
+ RefPtr<TextureClient> mTextureOfImage;
+ RefPtr<ImageClient> mImageClient;
+ Maybe<wr::PipelineId> mPipelineId;
+ RefPtr<ImageContainer> mContainer;
+};
+
+/// Holds some data used to share ImageLib results with the parent process.
+/// This may be either in the form of a blob recording or a rasterized surface.
+class WebRenderImageProviderData final : public WebRenderUserData {
+ public:
+ WebRenderImageProviderData(RenderRootStateManager* aManager,
+ nsDisplayItem* aItem);
+ WebRenderImageProviderData(RenderRootStateManager* aManager,
+ uint32_t aDisplayItemKey, nsIFrame* aFrame);
+ ~WebRenderImageProviderData() override;
+
+ WebRenderImageProviderData* AsImageProviderData() override { return this; }
+ UserDataType GetType() override { return UserDataType::eImageProvider; }
+ static UserDataType Type() { return UserDataType::eImageProvider; }
+
+ Maybe<wr::ImageKey> UpdateImageKey(image::WebRenderImageProvider* aProvider,
+ image::ImgDrawResult aDrawResult,
+ wr::IpcResourceUpdateQueue& aResources);
+
+ bool Invalidate(image::ImageProviderId aProviderId) const;
+
+ protected:
+ RefPtr<image::WebRenderImageProvider> mProvider;
+ Maybe<wr::ImageKey> mKey;
+ image::ImgDrawResult mDrawResult = image::ImgDrawResult::NOT_READY;
+};
+
+/// Used for fallback rendering.
+///
+/// In most cases this uses blob images but it can also render on the content
+/// side directly into a texture.
+class WebRenderFallbackData : public WebRenderUserData {
+ public:
+ WebRenderFallbackData(RenderRootStateManager* aManager, nsDisplayItem* aItem);
+ virtual ~WebRenderFallbackData();
+
+ WebRenderFallbackData* AsFallbackData() override { return this; }
+ UserDataType GetType() override { return UserDataType::eFallback; }
+ static UserDataType Type() { return UserDataType::eFallback; }
+ nsDisplayItemGeometry* GetGeometry() override { return mGeometry.get(); }
+
+ void SetInvalid(bool aInvalid) { mInvalid = aInvalid; }
+ bool IsInvalid() { return mInvalid; }
+ void SetFonts(const std::vector<RefPtr<gfx::ScaledFont>>& aFonts) {
+ mFonts = aFonts;
+ }
+ Maybe<wr::BlobImageKey> GetBlobImageKey() { return mBlobKey; }
+ void SetBlobImageKey(const wr::BlobImageKey& aKey);
+ Maybe<wr::ImageKey> GetImageKey();
+
+ /// Create a WebRenderImageData to manage the image we are about to render
+ /// into.
+ WebRenderImageData* PaintIntoImage();
+
+ gfx::DrawEventRecorderPrivate::ExternalSurfacesHolder mExternalSurfaces;
+ UniquePtr<nsDisplayItemGeometry> mGeometry;
+ DisplayItemClip mClip;
+ nsRect mBounds;
+ nsRect mBuildingRect;
+ gfx::MatrixScales mScale;
+ float mOpacity;
+
+ protected:
+ void ClearImageKey();
+
+ std::vector<RefPtr<gfx::ScaledFont>> mFonts;
+ Maybe<wr::BlobImageKey> mBlobKey;
+ // When rendering into a blob image, mImageData is null. It is non-null only
+ // when we render directly into a texture on the content side.
+ RefPtr<WebRenderImageData> mImageData;
+ bool mInvalid;
+};
+
+class WebRenderAPZAnimationData : public WebRenderUserData {
+ public:
+ WebRenderAPZAnimationData(RenderRootStateManager* aManager,
+ nsDisplayItem* aItem);
+ virtual ~WebRenderAPZAnimationData() = default;
+
+ UserDataType GetType() override { return UserDataType::eAPZAnimation; }
+ static UserDataType Type() { return UserDataType::eAPZAnimation; }
+ uint64_t GetAnimationId() { return mAnimationId; }
+
+ private:
+ uint64_t mAnimationId;
+};
+
+class WebRenderAnimationData : public WebRenderUserData {
+ public:
+ WebRenderAnimationData(RenderRootStateManager* aManager,
+ nsDisplayItem* aItem);
+ virtual ~WebRenderAnimationData();
+
+ UserDataType GetType() override { return UserDataType::eAnimation; }
+ static UserDataType Type() { return UserDataType::eAnimation; }
+ AnimationInfo& GetAnimationInfo() { return mAnimationInfo; }
+
+ protected:
+ AnimationInfo mAnimationInfo;
+};
+
+class WebRenderCanvasData : public WebRenderUserData {
+ public:
+ WebRenderCanvasData(RenderRootStateManager* aManager, nsDisplayItem* aItem);
+ virtual ~WebRenderCanvasData();
+
+ WebRenderCanvasData* AsCanvasData() override { return this; }
+ UserDataType GetType() override { return UserDataType::eCanvas; }
+ static UserDataType Type() { return UserDataType::eCanvas; }
+
+ void ClearCanvasRenderer();
+ WebRenderCanvasRendererAsync* GetCanvasRenderer();
+ WebRenderCanvasRendererAsync* CreateCanvasRenderer();
+ bool SetCanvasRenderer(CanvasRenderer* aCanvasRenderer);
+
+ void SetImageContainer(ImageContainer* aImageContainer);
+ ImageContainer* GetImageContainer();
+ void ClearImageContainer();
+
+ protected:
+ RefPtr<WebRenderCanvasRendererAsync> mCanvasRenderer;
+ RefPtr<ImageContainer> mContainer;
+};
+
+class WebRenderRemoteData : public WebRenderUserData {
+ public:
+ WebRenderRemoteData(RenderRootStateManager* aManager, nsDisplayItem* aItem);
+ virtual ~WebRenderRemoteData();
+
+ UserDataType GetType() override { return UserDataType::eRemote; }
+ static UserDataType Type() { return UserDataType::eRemote; }
+
+ void SetRemoteBrowser(dom::RemoteBrowser* aBrowser) {
+ mRemoteBrowser = aBrowser;
+ }
+
+ protected:
+ RefPtr<dom::RemoteBrowser> mRemoteBrowser;
+};
+
+class WebRenderMaskData : public WebRenderUserData {
+ public:
+ explicit WebRenderMaskData(RenderRootStateManager* aManager,
+ nsDisplayItem* aItem)
+ : WebRenderUserData(aManager, aItem),
+ mMaskStyle(nsStyleImageLayers::LayerType::Mask),
+ mShouldHandleOpacity(false) {
+ MOZ_COUNT_CTOR(WebRenderMaskData);
+ }
+ virtual ~WebRenderMaskData() {
+ MOZ_COUNT_DTOR(WebRenderMaskData);
+ ClearImageKey();
+ }
+
+ void ClearImageKey();
+ void Invalidate();
+
+ UserDataType GetType() override { return UserDataType::eMask; }
+ static UserDataType Type() { return UserDataType::eMask; }
+
+ Maybe<wr::BlobImageKey> mBlobKey;
+ std::vector<RefPtr<gfx::ScaledFont>> mFonts;
+ gfx::DrawEventRecorderPrivate::ExternalSurfacesHolder mExternalSurfaces;
+ LayerIntRect mItemRect;
+ nsPoint mMaskOffset;
+ nsStyleImageLayers mMaskStyle;
+ gfx::MatrixScales mScale;
+ bool mShouldHandleOpacity;
+};
+
+extern void DestroyWebRenderUserDataTable(WebRenderUserDataTable* aTable);
+
+struct WebRenderUserDataProperty {
+ NS_DECLARE_FRAME_PROPERTY_WITH_DTOR(Key, WebRenderUserDataTable,
+ DestroyWebRenderUserDataTable)
+};
+
+template <class T>
+already_AddRefed<T> GetWebRenderUserData(const nsIFrame* aFrame,
+ uint32_t aPerFrameKey) {
+ MOZ_ASSERT(aFrame);
+ WebRenderUserDataTable* userDataTable =
+ aFrame->GetProperty(WebRenderUserDataProperty::Key());
+ if (!userDataTable) {
+ return nullptr;
+ }
+
+ WebRenderUserData* data =
+ userDataTable->GetWeak(WebRenderUserDataKey(aPerFrameKey, T::Type()));
+ if (data) {
+ RefPtr<T> result = static_cast<T*>(data);
+ return result.forget();
+ }
+
+ return nullptr;
+}
+
+} // namespace layers
+} // namespace mozilla
+
+#endif /* GFX_WEBRENDERUSERDATA_H */