summaryrefslogtreecommitdiffstats
path: root/gfx/layers/wr/AsyncImagePipelineManager.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--gfx/layers/wr/AsyncImagePipelineManager.cpp746
1 files changed, 746 insertions, 0 deletions
diff --git a/gfx/layers/wr/AsyncImagePipelineManager.cpp b/gfx/layers/wr/AsyncImagePipelineManager.cpp
new file mode 100644
index 0000000000..c9848e6d7c
--- /dev/null
+++ b/gfx/layers/wr/AsyncImagePipelineManager.cpp
@@ -0,0 +1,746 @@
+/* -*- 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/CompositorThread.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()
+ : mInitialised(false),
+ mIsChanged(false),
+ mUseExternalImage(false),
+ mRotation(VideoInfo::Rotation::kDegree_0),
+ mFilter(wr::ImageRendering::Auto),
+ mMixBlendMode(wr::MixBlendMode::Normal) {}
+
+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
+ mUseWebRenderDCompVideoOverlayWin(
+ gfx::gfxVars::UseWebRenderDCompVideoOverlayWin()),
+#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() {
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ static uint64_t sResourceId = 0;
+
+ ++sResourceId;
+ // Upper 32bit(namespace) needs to be 0.
+ // Namespace other than 0 might be used by others.
+ MOZ_RELEASE_ASSERT(sResourceId != UINT32_MAX);
+ return wr::ToExternalImageId(sResourceId);
+}
+
+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;
+ }
+ uint64_t id = wr::AsUint64(aPipelineId);
+
+ PipelineTexturesHolder* holder =
+ mPipelineTexturesHolders.Get(wr::AsUint64(aPipelineId));
+ if (holder) {
+ // This could happen during tab move between different windows.
+ // Previously removed holder could be still alive for waiting destroyed.
+ MOZ_ASSERT(holder->mDestroyedEpoch.isSome());
+ holder->mDestroyedEpoch = Nothing(); // Revive holder
+ holder->mWrBridge = aWrBridge;
+ return;
+ }
+ holder = new PipelineTexturesHolder();
+ holder->mWrBridge = aWrBridge;
+ mPipelineTexturesHolders.Put(id, holder);
+}
+
+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.Get(id));
+ AsyncImagePipeline* holder = new AsyncImagePipeline();
+ holder->mImageHost = aImageHost;
+ mAsyncImagePipelines.Put(id, 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 VideoInfo::Rotation 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, nsTArray<wr::ImageKey>& aKeys,
+ wr::TransactionBuilder& aSceneBuilderTxn,
+ wr::TransactionBuilder& aMaybeFastTxn) {
+ MOZ_ASSERT(aKeys.IsEmpty());
+ MOZ_ASSERT(aPipeline);
+
+ TextureHost* texture =
+ aPipeline->mImageHost->GetAsTextureHostForComposite(this);
+ TextureHost* previousTexture = aPipeline->mCurrentTexture.get();
+
+ if (texture == previousTexture) {
+ // The texture has not changed, just reuse previous ImageKeys.
+ aKeys = aPipeline->mKeys.Clone();
+ return Nothing();
+ }
+
+ if (!texture || texture->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 = texture;
+
+ WebRenderTextureHost* wrTexture = texture->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 ? texture->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.
+ bool canUpdate = !!previousTexture &&
+ previousTexture->GetSize() == texture->GetSize() &&
+ previousTexture->GetFormat() == texture->GetFormat() &&
+ previousTexture->NeedsYFlip() == texture->NeedsYFlip() &&
+ previousTexture->SupportsExternalCompositing() ==
+ texture->SupportsExternalCompositing() &&
+ 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(texture, 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
+ // UseWebRenderDCompVideoOverlayWin() 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 = mUseWebRenderDCompVideoOverlayWin !=
+ gfx::gfxVars::UseWebRenderDCompVideoOverlayWin();
+ if (isChanged) {
+ mUseWebRenderDCompVideoOverlayWin =
+ gfx::gfxVars::UseWebRenderDCompVideoOverlayWin();
+ }
+#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 (auto iter = mAsyncImagePipelines.Iter(); !iter.Done(); iter.Next()) {
+ wr::PipelineId pipelineId = wr::AsPipelineId(iter.Key());
+ AsyncImagePipeline* pipeline = iter.UserData();
+
+#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;
+ }
+ ApplyAsyncImageForPipeline(epoch, pipelineId, pipeline, aSceneBuilderTxn,
+ aFastTxn);
+ }
+}
+
+wr::WrRotation ToWrRotation(VideoInfo::Rotation aRotation) {
+ switch (aRotation) {
+ case VideoInfo::Rotation::kDegree_0:
+ return wr::WrRotation::Degree0;
+ case VideoInfo::Rotation::kDegree_90:
+ return wr::WrRotation::Degree90;
+ case VideoInfo::Rotation::kDegree_180:
+ return wr::WrRotation::Degree180;
+ case VideoInfo::Rotation::kDegree_270:
+ return wr::WrRotation::Degree270;
+ }
+ return wr::WrRotation::Degree0;
+}
+
+void AsyncImagePipelineManager::ApplyAsyncImageForPipeline(
+ const wr::Epoch& aEpoch, const wr::PipelineId& aPipelineId,
+ AsyncImagePipeline* aPipeline, wr::TransactionBuilder& aSceneBuilderTxn,
+ wr::TransactionBuilder& aMaybeFastTxn) {
+ nsTArray<wr::ImageKey> keys;
+ auto op = UpdateImageKeys(aEpoch, aPipelineId, aPipeline, 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;
+
+ wr::DisplayListBuilder builder(aPipelineId);
+
+ 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 = ToWrRotation(aPipeline->mRotation);
+ params.computed_transform = &computedTransform;
+
+ Maybe<wr::WrSpatialId> referenceFrameId = builder.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(builder, 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;
+ if (IsOpaque(aPipeline->mCurrentTexture->GetFormat()) ||
+ bool(aPipeline->mCurrentTexture->GetFlags() &
+ TextureFlags::IS_OPAQUE)) {
+ flags += TextureHost::PushDisplayItemFlag::PREFER_COMPOSITOR_SURFACE;
+ }
+ if (mApi->SupportsExternalBufferTextures()) {
+ flags +=
+ TextureHost::PushDisplayItemFlag::SUPPORTS_EXTERNAL_BUFFER_TEXTURES;
+ }
+ aPipeline->mCurrentTexture->PushDisplayItems(
+ builder, wr::ToLayoutRect(rect), wr::ToLayoutRect(rect),
+ aPipeline->mFilter, range_keys, flags);
+ HoldExternalImage(aPipelineId, aEpoch, aPipeline->mCurrentTexture);
+ } else {
+ MOZ_ASSERT(keys.Length() == 1);
+ builder.PushImage(wr::ToLayoutRect(rect), wr::ToLayoutRect(rect), true,
+ aPipeline->mFilter, keys[0]);
+ }
+ }
+
+ spaceAndClipChainHelper.reset();
+ builder.PopStackingContext(referenceFrameId.isSome());
+
+ wr::BuiltDisplayList dl;
+ builder.Finalize(dl);
+ aSceneBuilderTxn.SetDisplayList(gfx::DeviceColor(0.f, 0.f, 0.f, 0.f), aEpoch,
+ wr::ToLayoutSize(aPipeline->mScBounds.Size()),
+ aPipelineId, dl.dl_desc, dl.dl);
+}
+
+void AsyncImagePipelineManager::ApplyAsyncImageForPipeline(
+ const wr::PipelineId& aPipelineId, wr::TransactionBuilder& aTxn,
+ wr::TransactionBuilder& aTxnForImageBridge) {
+ AsyncImagePipeline* pipeline =
+ mAsyncImagePipelines.Get(wr::AsUint64(aPipelineId));
+ if (!pipeline) {
+ return;
+ }
+ wr::TransactionBuilder fastTxn(/* 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();
+
+ ApplyAsyncImageForPipeline(epoch, aPipelineId, pipeline, sceneBuilderTxn,
+ maybeFastTxn);
+}
+
+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);
+
+ wr::BuiltDisplayList dl;
+ builder.Finalize(dl);
+ txn.SetDisplayList(gfx::DeviceColor(0.f, 0.f, 0.f, 0.f), epoch,
+ wr::ToLayoutSize(pipeline->mScBounds.Size()), aPipelineId,
+ dl.dl_desc, dl.dl);
+}
+
+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()) {
+ 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