diff options
Diffstat (limited to 'gfx/layers/wr')
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 */ |