diff options
Diffstat (limited to 'gfx/layers/wr')
40 files changed, 14898 insertions, 0 deletions
diff --git a/gfx/layers/wr/AsyncImagePipelineManager.cpp b/gfx/layers/wr/AsyncImagePipelineManager.cpp new file mode 100644 index 0000000000..c9848e6d7c --- /dev/null +++ b/gfx/layers/wr/AsyncImagePipelineManager.cpp @@ -0,0 +1,746 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "AsyncImagePipelineManager.h" + +#include <algorithm> +#include <iterator> + +#include "CompositableHost.h" +#include "gfxEnv.h" +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/layers/CompositorThread.h" +#include "mozilla/layers/SharedSurfacesParent.h" +#include "mozilla/layers/WebRenderImageHost.h" +#include "mozilla/layers/WebRenderTextureHost.h" +#include "mozilla/webrender/RenderThread.h" +#include "mozilla/webrender/WebRenderAPI.h" +#include "mozilla/webrender/WebRenderTypes.h" + +#ifdef MOZ_WIDGET_ANDROID +# include "mozilla/layers/TextureHostOGL.h" +#endif + +namespace mozilla { +namespace layers { + +AsyncImagePipelineManager::ForwardingExternalImage::~ForwardingExternalImage() { + DebugOnly<bool> released = SharedSurfacesParent::Release(mImageId); + MOZ_ASSERT(released); +} + +AsyncImagePipelineManager::AsyncImagePipeline::AsyncImagePipeline() + : mInitialised(false), + mIsChanged(false), + mUseExternalImage(false), + mRotation(VideoInfo::Rotation::kDegree_0), + mFilter(wr::ImageRendering::Auto), + mMixBlendMode(wr::MixBlendMode::Normal) {} + +AsyncImagePipelineManager::AsyncImagePipelineManager( + RefPtr<wr::WebRenderAPI>&& aApi, bool aUseCompositorWnd) + : mApi(aApi), + mUseCompositorWnd(aUseCompositorWnd), + mIdNamespace(mApi->GetNamespace()), + mUseTripleBuffering(mApi->GetUseTripleBuffering()), + mResourceId(0), + mAsyncImageEpoch{0}, + mWillGenerateFrame(false), + mDestroyed(false), +#ifdef XP_WIN + mUseWebRenderDCompVideoOverlayWin( + gfx::gfxVars::UseWebRenderDCompVideoOverlayWin()), +#endif + mRenderSubmittedUpdatesLock("SubmittedUpdatesLock"), + mLastCompletedFrameId(0) { + MOZ_COUNT_CTOR(AsyncImagePipelineManager); +} + +AsyncImagePipelineManager::~AsyncImagePipelineManager() { + MOZ_COUNT_DTOR(AsyncImagePipelineManager); +} + +void AsyncImagePipelineManager::Destroy() { + MOZ_ASSERT(!mDestroyed); + mApi = nullptr; + mPipelineTexturesHolders.Clear(); + mDestroyed = true; +} + +/* static */ +wr::ExternalImageId AsyncImagePipelineManager::GetNextExternalImageId() { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + static uint64_t sResourceId = 0; + + ++sResourceId; + // Upper 32bit(namespace) needs to be 0. + // Namespace other than 0 might be used by others. + MOZ_RELEASE_ASSERT(sResourceId != UINT32_MAX); + return wr::ToExternalImageId(sResourceId); +} + +void AsyncImagePipelineManager::SetWillGenerateFrame() { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + + mWillGenerateFrame = true; +} + +bool AsyncImagePipelineManager::GetAndResetWillGenerateFrame() { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + + bool ret = mWillGenerateFrame; + mWillGenerateFrame = false; + return ret; +} + +void AsyncImagePipelineManager::AddPipeline(const wr::PipelineId& aPipelineId, + WebRenderBridgeParent* aWrBridge) { + if (mDestroyed) { + return; + } + uint64_t id = wr::AsUint64(aPipelineId); + + PipelineTexturesHolder* holder = + mPipelineTexturesHolders.Get(wr::AsUint64(aPipelineId)); + if (holder) { + // This could happen during tab move between different windows. + // Previously removed holder could be still alive for waiting destroyed. + MOZ_ASSERT(holder->mDestroyedEpoch.isSome()); + holder->mDestroyedEpoch = Nothing(); // Revive holder + holder->mWrBridge = aWrBridge; + return; + } + holder = new PipelineTexturesHolder(); + holder->mWrBridge = aWrBridge; + mPipelineTexturesHolders.Put(id, holder); +} + +void AsyncImagePipelineManager::RemovePipeline( + const wr::PipelineId& aPipelineId, const wr::Epoch& aEpoch) { + if (mDestroyed) { + return; + } + + PipelineTexturesHolder* holder = + mPipelineTexturesHolders.Get(wr::AsUint64(aPipelineId)); + MOZ_ASSERT(holder); + if (!holder) { + return; + } + holder->mWrBridge = nullptr; + holder->mDestroyedEpoch = Some(aEpoch); +} + +WebRenderBridgeParent* AsyncImagePipelineManager::GetWrBridge( + const wr::PipelineId& aPipelineId) { + if (mDestroyed) { + return nullptr; + } + + PipelineTexturesHolder* holder = + mPipelineTexturesHolders.Get(wr::AsUint64(aPipelineId)); + if (!holder) { + return nullptr; + } + if (holder->mWrBridge) { + MOZ_ASSERT(holder->mDestroyedEpoch.isNothing()); + return holder->mWrBridge; + } + + return nullptr; +} + +void AsyncImagePipelineManager::AddAsyncImagePipeline( + const wr::PipelineId& aPipelineId, WebRenderImageHost* aImageHost) { + if (mDestroyed) { + return; + } + MOZ_ASSERT(aImageHost); + uint64_t id = wr::AsUint64(aPipelineId); + + MOZ_ASSERT(!mAsyncImagePipelines.Get(id)); + AsyncImagePipeline* holder = new AsyncImagePipeline(); + holder->mImageHost = aImageHost; + mAsyncImagePipelines.Put(id, holder); + AddPipeline(aPipelineId, /* aWrBridge */ nullptr); +} + +void AsyncImagePipelineManager::RemoveAsyncImagePipeline( + const wr::PipelineId& aPipelineId, wr::TransactionBuilder& aTxn) { + if (mDestroyed) { + return; + } + + uint64_t id = wr::AsUint64(aPipelineId); + if (auto entry = mAsyncImagePipelines.Lookup(id)) { + const auto& holder = entry.Data(); + wr::Epoch epoch = GetNextImageEpoch(); + aTxn.ClearDisplayList(epoch, aPipelineId); + for (wr::ImageKey key : holder->mKeys) { + aTxn.DeleteImage(key); + } + entry.Remove(); + RemovePipeline(aPipelineId, epoch); + } +} + +void AsyncImagePipelineManager::UpdateAsyncImagePipeline( + const wr::PipelineId& aPipelineId, const LayoutDeviceRect& aScBounds, + const VideoInfo::Rotation aRotation, const wr::ImageRendering& aFilter, + const wr::MixBlendMode& aMixBlendMode) { + if (mDestroyed) { + return; + } + AsyncImagePipeline* pipeline = + mAsyncImagePipelines.Get(wr::AsUint64(aPipelineId)); + if (!pipeline) { + return; + } + pipeline->mInitialised = true; + pipeline->Update(aScBounds, aRotation, aFilter, aMixBlendMode); +} + +Maybe<TextureHost::ResourceUpdateOp> AsyncImagePipelineManager::UpdateImageKeys( + const wr::Epoch& aEpoch, const wr::PipelineId& aPipelineId, + AsyncImagePipeline* aPipeline, nsTArray<wr::ImageKey>& aKeys, + wr::TransactionBuilder& aSceneBuilderTxn, + wr::TransactionBuilder& aMaybeFastTxn) { + MOZ_ASSERT(aKeys.IsEmpty()); + MOZ_ASSERT(aPipeline); + + TextureHost* texture = + aPipeline->mImageHost->GetAsTextureHostForComposite(this); + TextureHost* previousTexture = aPipeline->mCurrentTexture.get(); + + if (texture == previousTexture) { + // The texture has not changed, just reuse previous ImageKeys. + aKeys = aPipeline->mKeys.Clone(); + return Nothing(); + } + + if (!texture || texture->NumSubTextures() == 0) { + // We don't have a new texture or texture does not have SubTextures, there + // isn't much we can do. + aKeys = aPipeline->mKeys.Clone(); + return Nothing(); + } + + aPipeline->mCurrentTexture = texture; + + WebRenderTextureHost* wrTexture = texture->AsWebRenderTextureHost(); + MOZ_ASSERT(wrTexture); + if (!wrTexture) { + gfxCriticalNote << "WebRenderTextureHost is not used"; + } + + bool useExternalImage = !!wrTexture; + aPipeline->mUseExternalImage = useExternalImage; + + // The non-external image code path falls back to converting the texture into + // an rgb image. + auto numKeys = useExternalImage ? texture->NumSubTextures() : 1; + MOZ_ASSERT(numKeys > 0); + + // If we already had a texture and the format hasn't changed, better to reuse + // the image keys than create new ones. + bool canUpdate = !!previousTexture && + previousTexture->GetSize() == texture->GetSize() && + previousTexture->GetFormat() == texture->GetFormat() && + previousTexture->NeedsYFlip() == texture->NeedsYFlip() && + previousTexture->SupportsExternalCompositing() == + texture->SupportsExternalCompositing() && + aPipeline->mKeys.Length() == numKeys; + + if (!canUpdate) { + for (auto key : aPipeline->mKeys) { + // Destroy ImageKeys on transaction of scene builder thread, since + // DisplayList is updated on SceneBuilder thread. It prevents too early + // ImageKey deletion. + aSceneBuilderTxn.DeleteImage(key); + } + aPipeline->mKeys.Clear(); + for (uint32_t i = 0; i < numKeys; ++i) { + aPipeline->mKeys.AppendElement(GenerateImageKey()); + } + } + + aKeys = aPipeline->mKeys.Clone(); + + auto op = canUpdate ? TextureHost::UPDATE_IMAGE : TextureHost::ADD_IMAGE; + + if (!useExternalImage) { + return UpdateWithoutExternalImage(texture, aKeys[0], op, aMaybeFastTxn); + } + + wrTexture->MaybeNotifyForUse(aMaybeFastTxn); + + Range<wr::ImageKey> keys(&aKeys[0], aKeys.Length()); + auto externalImageKey = wrTexture->GetExternalImageKey(); + wrTexture->PushResourceUpdates(aMaybeFastTxn, op, keys, externalImageKey); + + return Some(op); +} + +Maybe<TextureHost::ResourceUpdateOp> +AsyncImagePipelineManager::UpdateWithoutExternalImage( + TextureHost* aTexture, wr::ImageKey aKey, TextureHost::ResourceUpdateOp aOp, + wr::TransactionBuilder& aTxn) { + MOZ_ASSERT(aTexture); + + RefPtr<gfx::DataSourceSurface> dSurf = aTexture->GetAsSurface(); + if (!dSurf) { + NS_ERROR("TextureHost does not return DataSourceSurface"); + return Nothing(); + } + gfx::DataSourceSurface::MappedSurface map; + if (!dSurf->Map(gfx::DataSourceSurface::MapType::READ, &map)) { + NS_ERROR("DataSourceSurface failed to map"); + return Nothing(); + } + + gfx::IntSize size = dSurf->GetSize(); + wr::ImageDescriptor descriptor(size, map.mStride, dSurf->GetFormat()); + + // Costly copy right here... + wr::Vec<uint8_t> bytes; + bytes.PushBytes(Range<uint8_t>(map.mData, size.height * map.mStride)); + + if (aOp == TextureHost::UPDATE_IMAGE) { + aTxn.UpdateImageBuffer(aKey, descriptor, bytes); + } else { + aTxn.AddImage(aKey, descriptor, bytes); + } + + dSurf->Unmap(); + + return Some(aOp); +} + +void AsyncImagePipelineManager::ApplyAsyncImagesOfImageBridge( + wr::TransactionBuilder& aSceneBuilderTxn, + wr::TransactionBuilder& aFastTxn) { + if (mDestroyed || mAsyncImagePipelines.Count() == 0) { + return; + } + +#ifdef XP_WIN + // UseWebRenderDCompVideoOverlayWin() could be changed from true to false, + // when DCompVideoOverlay task is failed. In this case, DisplayItems need to + // be re-pushed to WebRender for disabling video overlay. + bool isChanged = mUseWebRenderDCompVideoOverlayWin != + gfx::gfxVars::UseWebRenderDCompVideoOverlayWin(); + if (isChanged) { + mUseWebRenderDCompVideoOverlayWin = + gfx::gfxVars::UseWebRenderDCompVideoOverlayWin(); + } +#endif + + wr::Epoch epoch = GetNextImageEpoch(); + + // We use a pipeline with a very small display list for each video element. + // Update each of them if needed. + for (auto iter = mAsyncImagePipelines.Iter(); !iter.Done(); iter.Next()) { + wr::PipelineId pipelineId = wr::AsPipelineId(iter.Key()); + AsyncImagePipeline* pipeline = iter.UserData(); + +#ifdef XP_WIN + if (isChanged) { + pipeline->mIsChanged = true; + } +#endif + + // If aync image pipeline does not use ImageBridge, do not need to apply. + if (!pipeline->mImageHost->GetAsyncRef()) { + continue; + } + ApplyAsyncImageForPipeline(epoch, pipelineId, pipeline, aSceneBuilderTxn, + aFastTxn); + } +} + +wr::WrRotation ToWrRotation(VideoInfo::Rotation aRotation) { + switch (aRotation) { + case VideoInfo::Rotation::kDegree_0: + return wr::WrRotation::Degree0; + case VideoInfo::Rotation::kDegree_90: + return wr::WrRotation::Degree90; + case VideoInfo::Rotation::kDegree_180: + return wr::WrRotation::Degree180; + case VideoInfo::Rotation::kDegree_270: + return wr::WrRotation::Degree270; + } + return wr::WrRotation::Degree0; +} + +void AsyncImagePipelineManager::ApplyAsyncImageForPipeline( + const wr::Epoch& aEpoch, const wr::PipelineId& aPipelineId, + AsyncImagePipeline* aPipeline, wr::TransactionBuilder& aSceneBuilderTxn, + wr::TransactionBuilder& aMaybeFastTxn) { + nsTArray<wr::ImageKey> keys; + auto op = UpdateImageKeys(aEpoch, aPipelineId, aPipeline, keys, + aSceneBuilderTxn, aMaybeFastTxn); + + bool updateDisplayList = + aPipeline->mInitialised && + (aPipeline->mIsChanged || op == Some(TextureHost::ADD_IMAGE)) && + !!aPipeline->mCurrentTexture; + + if (!updateDisplayList) { + // We don't need to update the display list, either because we can't or + // because the previous one is still up to date. We may, however, have + // updated some resources. + + // Use transaction of scene builder thread to notify epoch. + // It is for making epoch update consistent. + aSceneBuilderTxn.UpdateEpoch(aPipelineId, aEpoch); + if (aPipeline->mCurrentTexture) { + HoldExternalImage(aPipelineId, aEpoch, aPipeline->mCurrentTexture); + } + return; + } + + aPipeline->mIsChanged = false; + + wr::DisplayListBuilder builder(aPipelineId); + + float opacity = 1.0f; + wr::StackingContextParams params; + params.opacity = &opacity; + params.mix_blend_mode = aPipeline->mMixBlendMode; + + wr::WrComputedTransformData computedTransform; + computedTransform.vertical_flip = + aPipeline->mCurrentTexture && aPipeline->mCurrentTexture->NeedsYFlip(); + computedTransform.scale_from = { + float(aPipeline->mCurrentTexture->GetSize().width), + float(aPipeline->mCurrentTexture->GetSize().height)}; + computedTransform.rotation = ToWrRotation(aPipeline->mRotation); + params.computed_transform = &computedTransform; + + Maybe<wr::WrSpatialId> referenceFrameId = builder.PushStackingContext( + params, wr::ToLayoutRect(aPipeline->mScBounds), + // This is fine to do unconditionally because we only push images here. + wr::RasterSpace::Screen()); + + Maybe<wr::SpaceAndClipChainHelper> spaceAndClipChainHelper; + if (referenceFrameId) { + spaceAndClipChainHelper.emplace(builder, referenceFrameId.ref()); + } + + if (aPipeline->mCurrentTexture && !keys.IsEmpty()) { + LayoutDeviceRect rect(0, 0, aPipeline->mCurrentTexture->GetSize().width, + aPipeline->mCurrentTexture->GetSize().height); + + if (aPipeline->mUseExternalImage) { + MOZ_ASSERT(aPipeline->mCurrentTexture->AsWebRenderTextureHost()); + Range<wr::ImageKey> range_keys(&keys[0], keys.Length()); + TextureHost::PushDisplayItemFlagSet flags; + if (IsOpaque(aPipeline->mCurrentTexture->GetFormat()) || + bool(aPipeline->mCurrentTexture->GetFlags() & + TextureFlags::IS_OPAQUE)) { + flags += TextureHost::PushDisplayItemFlag::PREFER_COMPOSITOR_SURFACE; + } + if (mApi->SupportsExternalBufferTextures()) { + flags += + TextureHost::PushDisplayItemFlag::SUPPORTS_EXTERNAL_BUFFER_TEXTURES; + } + aPipeline->mCurrentTexture->PushDisplayItems( + builder, wr::ToLayoutRect(rect), wr::ToLayoutRect(rect), + aPipeline->mFilter, range_keys, flags); + HoldExternalImage(aPipelineId, aEpoch, aPipeline->mCurrentTexture); + } else { + MOZ_ASSERT(keys.Length() == 1); + builder.PushImage(wr::ToLayoutRect(rect), wr::ToLayoutRect(rect), true, + aPipeline->mFilter, keys[0]); + } + } + + spaceAndClipChainHelper.reset(); + builder.PopStackingContext(referenceFrameId.isSome()); + + wr::BuiltDisplayList dl; + builder.Finalize(dl); + aSceneBuilderTxn.SetDisplayList(gfx::DeviceColor(0.f, 0.f, 0.f, 0.f), aEpoch, + wr::ToLayoutSize(aPipeline->mScBounds.Size()), + aPipelineId, dl.dl_desc, dl.dl); +} + +void AsyncImagePipelineManager::ApplyAsyncImageForPipeline( + const wr::PipelineId& aPipelineId, wr::TransactionBuilder& aTxn, + wr::TransactionBuilder& aTxnForImageBridge) { + AsyncImagePipeline* pipeline = + mAsyncImagePipelines.Get(wr::AsUint64(aPipelineId)); + if (!pipeline) { + return; + } + wr::TransactionBuilder fastTxn(/* aUseSceneBuilderThread */ false); + wr::AutoTransactionSender sender(mApi, &fastTxn); + + // Transaction for async image pipeline that uses ImageBridge always need to + // be non low priority. + auto& sceneBuilderTxn = + pipeline->mImageHost->GetAsyncRef() ? aTxnForImageBridge : aTxn; + + // Use transaction of using non scene builder thread when ImageHost uses + // ImageBridge. ApplyAsyncImagesOfImageBridge() handles transaction of adding + // and updating wr::ImageKeys of ImageHosts that uses ImageBridge. Then + // AsyncImagePipelineManager always needs to use non scene builder thread + // transaction for adding and updating wr::ImageKeys of ImageHosts that uses + // ImageBridge. Otherwise, ordering of wr::ImageKeys updating in webrender + // becomes inconsistent. + auto& maybeFastTxn = pipeline->mImageHost->GetAsyncRef() ? fastTxn : aTxn; + + wr::Epoch epoch = GetNextImageEpoch(); + + ApplyAsyncImageForPipeline(epoch, aPipelineId, pipeline, sceneBuilderTxn, + maybeFastTxn); +} + +void AsyncImagePipelineManager::SetEmptyDisplayList( + const wr::PipelineId& aPipelineId, wr::TransactionBuilder& aTxn, + wr::TransactionBuilder& aTxnForImageBridge) { + AsyncImagePipeline* pipeline = + mAsyncImagePipelines.Get(wr::AsUint64(aPipelineId)); + if (!pipeline) { + return; + } + + // Transaction for async image pipeline that uses ImageBridge always need to + // be non low priority. + auto& txn = pipeline->mImageHost->GetAsyncRef() ? aTxnForImageBridge : aTxn; + + wr::Epoch epoch = GetNextImageEpoch(); + wr::DisplayListBuilder builder(aPipelineId); + + wr::BuiltDisplayList dl; + builder.Finalize(dl); + txn.SetDisplayList(gfx::DeviceColor(0.f, 0.f, 0.f, 0.f), epoch, + wr::ToLayoutSize(pipeline->mScBounds.Size()), aPipelineId, + dl.dl_desc, dl.dl); +} + +void AsyncImagePipelineManager::HoldExternalImage( + const wr::PipelineId& aPipelineId, const wr::Epoch& aEpoch, + TextureHost* aTexture) { + if (mDestroyed) { + return; + } + MOZ_ASSERT(aTexture); + + PipelineTexturesHolder* holder = + mPipelineTexturesHolders.Get(wr::AsUint64(aPipelineId)); + MOZ_ASSERT(holder); + if (!holder) { + return; + } + if (aTexture->NeedsDeferredDeletion()) { + // Hold WebRenderTextureHost until rendering completed. + holder->mTextureHostsUntilRenderCompleted.emplace_back( + MakeUnique<ForwardingTextureHost>(aEpoch, aTexture)); + } else { + // Hold WebRenderTextureHost until submitted for rendering. + holder->mTextureHostsUntilRenderSubmitted.emplace_back(aEpoch, aTexture); + } +} + +void AsyncImagePipelineManager::HoldExternalImage( + const wr::PipelineId& aPipelineId, const wr::Epoch& aEpoch, + const wr::ExternalImageId& aImageId) { + if (mDestroyed) { + SharedSurfacesParent::Release(aImageId); + return; + } + + PipelineTexturesHolder* holder = + mPipelineTexturesHolders.Get(wr::AsUint64(aPipelineId)); + MOZ_ASSERT(holder); + if (!holder) { + SharedSurfacesParent::Release(aImageId); + return; + } + + holder->mExternalImages.emplace_back( + MakeUnique<ForwardingExternalImage>(aEpoch, aImageId)); +} + +void AsyncImagePipelineManager::NotifyPipelinesUpdated( + RefPtr<const wr::WebRenderPipelineInfo> aInfo, + wr::RenderedFrameId aLatestFrameId, + wr::RenderedFrameId aLastCompletedFrameId, ipc::FileDescriptor&& aFenceFd) { + MOZ_ASSERT(wr::RenderThread::IsInRenderThread()); + MOZ_ASSERT(mLastCompletedFrameId <= aLastCompletedFrameId.mId); + MOZ_ASSERT(aLatestFrameId.IsValid()); + + mLastCompletedFrameId = aLastCompletedFrameId.mId; + + { + // We need to lock for mRenderSubmittedUpdates because it can be accessed + // on the compositor thread. + MutexAutoLock lock(mRenderSubmittedUpdatesLock); + + // Move the pending updates into the submitted ones. + mRenderSubmittedUpdates.emplace_back( + aLatestFrameId, + WebRenderPipelineInfoHolder(std::move(aInfo), std::move(aFenceFd))); + } + + // Queue a runnable on the compositor thread to process the updates. + // This will also call CheckForTextureHostsNotUsedByGPU. + layers::CompositorThread()->Dispatch( + NewRunnableMethod("ProcessPipelineUpdates", this, + &AsyncImagePipelineManager::ProcessPipelineUpdates)); +} + +void AsyncImagePipelineManager::ProcessPipelineUpdates() { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + + if (mDestroyed) { + return; + } + + std::vector<std::pair<wr::RenderedFrameId, WebRenderPipelineInfoHolder>> + submittedUpdates; + { + // We need to lock for mRenderSubmittedUpdates because it can be accessed on + // the compositor thread. + MutexAutoLock lock(mRenderSubmittedUpdatesLock); + mRenderSubmittedUpdates.swap(submittedUpdates); + } + + // submittedUpdates is a vector of RenderedFrameIds paired with vectors of + // WebRenderPipelineInfo. + for (auto update : submittedUpdates) { + auto& holder = update.second; + const auto& info = holder.mInfo->Raw(); + + mReleaseFenceFd = std::move(holder.mFenceFd); + + for (auto& epoch : info.epochs) { + ProcessPipelineRendered(epoch.pipeline_id, epoch.epoch, update.first); + } + for (auto& removedPipeline : info.removed_pipelines) { + ProcessPipelineRemoved(removedPipeline, update.first); + } + } + CheckForTextureHostsNotUsedByGPU(); +} + +void AsyncImagePipelineManager::ProcessPipelineRendered( + const wr::PipelineId& aPipelineId, const wr::Epoch& aEpoch, + wr::RenderedFrameId aRenderedFrameId) { + if (auto entry = mPipelineTexturesHolders.Lookup(wr::AsUint64(aPipelineId))) { + const auto& holder = entry.Data(); + // For TextureHosts that can be released on render submission, using aEpoch + // find the first that we can't release and then release all prior to that. + auto firstSubmittedHostToKeep = std::find_if( + holder->mTextureHostsUntilRenderSubmitted.begin(), + holder->mTextureHostsUntilRenderSubmitted.end(), + [&aEpoch](const auto& entry) { return aEpoch <= entry.mEpoch; }); +#ifdef MOZ_WIDGET_ANDROID + // Set release fence if TextureHost owns AndroidHardwareBuffer. + // The TextureHost handled by mTextureHostsUntilRenderSubmitted instead of + // mTextureHostsUntilRenderCompleted, since android fence could be used + // to wait until its end of usage by GPU. + for (auto it = holder->mTextureHostsUntilRenderSubmitted.begin(); + it != firstSubmittedHostToKeep; ++it) { + const auto& entry = it; + if (entry->mTexture->GetAndroidHardwareBuffer()) { + ipc::FileDescriptor fenceFd = mReleaseFenceFd; + entry->mTexture->SetReleaseFence(std::move(fenceFd)); + } + } +#endif + holder->mTextureHostsUntilRenderSubmitted.erase( + holder->mTextureHostsUntilRenderSubmitted.begin(), + firstSubmittedHostToKeep); + + // For TextureHosts that need to wait until render completed, find the first + // that is later than aEpoch and then move all prior to that to + // mTexturesInUseByGPU paired with aRenderedFrameId. These will be released + // once rendering has completed for aRenderedFrameId. + auto firstCompletedHostToKeep = std::find_if( + holder->mTextureHostsUntilRenderCompleted.begin(), + holder->mTextureHostsUntilRenderCompleted.end(), + [&aEpoch](const auto& entry) { return aEpoch <= entry->mEpoch; }); + if (firstCompletedHostToKeep != + holder->mTextureHostsUntilRenderCompleted.begin()) { + std::vector<UniquePtr<ForwardingTextureHost>> hostsUntilCompleted( + std::make_move_iterator( + holder->mTextureHostsUntilRenderCompleted.begin()), + std::make_move_iterator(firstCompletedHostToKeep)); + mTexturesInUseByGPU.emplace_back(aRenderedFrameId, + std::move(hostsUntilCompleted)); + holder->mTextureHostsUntilRenderCompleted.erase( + holder->mTextureHostsUntilRenderCompleted.begin(), + firstCompletedHostToKeep); + } + + // Using aEpoch, find the first external image that we can't release and + // then release all prior to that. + auto firstImageToKeep = std::find_if( + holder->mExternalImages.begin(), holder->mExternalImages.end(), + [&aEpoch](const auto& entry) { return aEpoch <= entry->mEpoch; }); + holder->mExternalImages.erase(holder->mExternalImages.begin(), + firstImageToKeep); + } +} + +void AsyncImagePipelineManager::ProcessPipelineRemoved( + const wr::RemovedPipeline& aRemovedPipeline, + wr::RenderedFrameId aRenderedFrameId) { + if (mDestroyed) { + return; + } + if (auto entry = mPipelineTexturesHolders.Lookup( + wr::AsUint64(aRemovedPipeline.pipeline_id))) { + const auto& holder = entry.Data(); + if (holder->mDestroyedEpoch.isSome()) { + if (!holder->mTextureHostsUntilRenderCompleted.empty()) { + // Move all TextureHosts that must be held until render completed to + // mTexturesInUseByGPU paired with aRenderedFrameId. + mTexturesInUseByGPU.emplace_back( + aRenderedFrameId, + std::move(holder->mTextureHostsUntilRenderCompleted)); + } + + // Remove Pipeline releasing all remaining TextureHosts and external + // images. + entry.Remove(); + } + + // If mDestroyedEpoch contains nothing it means we reused the same pipeline + // id (probably because we moved the tab to another window). In this case we + // need to keep the holder. + } +} + +void AsyncImagePipelineManager::CheckForTextureHostsNotUsedByGPU() { + uint64_t lastCompletedFrameId = mLastCompletedFrameId; + + // Find first entry after mLastCompletedFrameId and release all prior ones. + auto firstTexturesToKeep = + std::find_if(mTexturesInUseByGPU.begin(), mTexturesInUseByGPU.end(), + [lastCompletedFrameId](const auto& entry) { + return lastCompletedFrameId < entry.first.mId; + }); + mTexturesInUseByGPU.erase(mTexturesInUseByGPU.begin(), firstTexturesToKeep); +} + +wr::Epoch AsyncImagePipelineManager::GetNextImageEpoch() { + mAsyncImageEpoch.mHandle++; + return mAsyncImageEpoch; +} + +AsyncImagePipelineManager::WebRenderPipelineInfoHolder:: + WebRenderPipelineInfoHolder(RefPtr<const wr::WebRenderPipelineInfo>&& aInfo, + ipc::FileDescriptor&& aFenceFd) + : mInfo(aInfo), mFenceFd(aFenceFd) {} + +AsyncImagePipelineManager::WebRenderPipelineInfoHolder:: + ~WebRenderPipelineInfoHolder() = default; + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/wr/AsyncImagePipelineManager.h b/gfx/layers/wr/AsyncImagePipelineManager.h new file mode 100644 index 0000000000..0d41949482 --- /dev/null +++ b/gfx/layers/wr/AsyncImagePipelineManager.h @@ -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/. */ + +#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/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, + VideoInfo::Rotation 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); + + 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(); + + 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(); + void Update(const LayoutDeviceRect& aScBounds, + VideoInfo::Rotation 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; + VideoInfo::Rotation mRotation; + wr::ImageRendering mFilter; + wr::MixBlendMode mMixBlendMode; + RefPtr<WebRenderImageHost> mImageHost; + CompositableTextureHostRef mCurrentTexture; + nsTArray<wr::ImageKey> mKeys; + }; + + void ApplyAsyncImageForPipeline(const wr::Epoch& aEpoch, + const wr::PipelineId& aPipelineId, + AsyncImagePipeline* aPipeline, + wr::TransactionBuilder& aSceneBuilderTxn, + wr::TransactionBuilder& aMaybeFastTxn); + Maybe<TextureHost::ResourceUpdateOp> UpdateImageKeys( + const wr::Epoch& aEpoch, const wr::PipelineId& aPipelineId, + AsyncImagePipeline* aPipeline, 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; + + 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 mUseWebRenderDCompVideoOverlayWin; +#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; + + 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/ClipManager.cpp b/gfx/layers/wr/ClipManager.cpp new file mode 100644 index 0000000000..c1347d4210 --- /dev/null +++ b/gfx/layers/wr/ClipManager.cpp @@ -0,0 +1,424 @@ +/* -*- 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/layers/StackingContextHelper.h" +#include "mozilla/layers/WebRenderLayerManager.h" +#include "mozilla/webrender/WebRenderAPI.h" +#include "nsDisplayList.h" +#include "nsStyleStructInlines.h" +#include "UnitTransforms.h" + +// clang-format off +#define CLIP_LOG(...) +//#define CLIP_LOG(...) printf_stderr("CLIP: " __VA_ARGS__) +//#define CLIP_LOG(...) if (XRE_IsContentProcess()) printf_stderr("CLIP: " __VA_ARGS__) +// clang-format on + +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) { + if (aStackingContext.AffectsClipPositioning()) { + if (aStackingContext.ReferenceFrameId()) { + PushOverrideForASR( + mItemClipStack.empty() ? nullptr : mItemClipStack.top().mASR, + aStackingContext.ReferenceFrameId().ref()); + } else { + // Start a new cache + mCacheStack.emplace(); + } + } + + ItemClips clips(nullptr, nullptr, false); + if (!mItemClipStack.empty()) { + clips.CopyOutputsFrom(mItemClipStack.top()); + } + + if (aStackingContext.ReferenceFrameId()) { + clips.mScrollId = aStackingContext.ReferenceFrameId().ref(); + } + + mItemClipStack.push(clips); +} + +void ClipManager::EndList(const StackingContextHelper& aStackingContext) { + MOZ_ASSERT(!mItemClipStack.empty()); + 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) { + Maybe<wr::WrSpaceAndClip> spaceAndClip = GetScrollLayer(aASR); + MOZ_ASSERT(spaceAndClip.isSome()); + + CLIP_LOG("Pushing %p override %zu -> %s\n", aASR, spaceAndClip->space.id, + Stringify(aSpatialId.id).c_str()); + + auto it = + mASROverride.insert({spaceAndClip->space, std::stack<wr::WrSpatialId>()}); + it.first->second.push(aSpatialId); + + // Start a new cache + mCacheStack.emplace(); +} + +void ClipManager::PopOverrideForASR(const ActiveScrolledRoot* aASR) { + MOZ_ASSERT(!mCacheStack.empty()); + mCacheStack.pop(); + + Maybe<wr::WrSpaceAndClip> spaceAndClip = GetScrollLayer(aASR); + MOZ_ASSERT(spaceAndClip.isSome()); + + auto it = mASROverride.find(spaceAndClip->space); + CLIP_LOG("Popping %p override %zu -> %s\n", aASR, spaceAndClip->space.id, + Stringify(it->second.top().id).c_str()); + + it->second.pop(); + if (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 %s\n", aSpatialId.id, + Stringify(it->second.top().id).c_str()); + + return it->second.top(); +} + +wr::WrSpaceAndClipChain ClipManager::SwitchItem(nsDisplayItem* aItem) { + const DisplayItemClipChain* clip = aItem->GetClipChain(); + const ActiveScrolledRoot* asr = aItem->GetActiveScrolledRoot(); + CLIP_LOG("processing item %p (%s) asr %p\n", aItem, + DisplayItemTypeName(aItem->GetType()), asr); + + 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. + nsDisplayStickyPosition* 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; + } + } + + // 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() == nullptr; + } + + ItemClips clips(asr, clip, 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(); + + // 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. + int32_t auPerDevPixel; + if (type == DisplayItemType::TYPE_ZOOM) { + auPerDevPixel = + static_cast<nsDisplayZoom*>(aItem)->GetParentAppUnitsPerDevPixel(); + } else { + auPerDevPixel = aItem->Frame()->PresContext()->AppUnitsPerDevPixel(); + } + + // 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::WrSpaceAndClip> 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); + + Maybe<wr::WrSpaceAndClip> spaceAndClip = GetScrollLayer(asr); + MOZ_ASSERT(spaceAndClip.isSome()); + clips.mScrollId = SpatialIdAfterOverride(spaceAndClip->space); + CLIP_LOG("\tassigning %d -> %d\n", (int)spaceAndClip->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(); + mItemClipStack.push(clips); + + CLIP_LOG("done setup for %p\n", aItem); + return spaceAndClipChain; +} + +Maybe<wr::WrSpaceAndClip> ClipManager::GetScrollLayer( + const ActiveScrolledRoot* aASR) { + for (const ActiveScrolledRoot* asr = aASR; asr; asr = asr->mParent) { + Maybe<wr::WrSpaceAndClip> spaceAndClip = + mBuilder->GetScrollIdForDefinedScrollLayer(asr->GetViewId()); + if (spaceAndClip) { + return spaceAndClip; + } + + // 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::WrSpaceAndClip> spaceAndClip = + mBuilder->GetScrollIdForDefinedScrollLayer( + ScrollableLayerGuid::NULL_SCROLL_ID); + MOZ_ASSERT(spaceAndClip.isSome()); + return spaceAndClip; +} + +Maybe<wr::WrSpaceAndClip> ClipManager::DefineScrollLayers( + const ActiveScrolledRoot* aASR, nsDisplayItem* aItem) { + if (!aASR) { + // Recursion base case + return Nothing(); + } + ScrollableLayerGuid::ViewID viewId = aASR->GetViewId(); + Maybe<wr::WrSpaceAndClip> spaceAndClip = + mBuilder->GetScrollIdForDefinedScrollLayer(viewId); + if (spaceAndClip) { + // If we've already defined this scroll layer before, we can early-exit + return spaceAndClip; + } + // Recurse to define the ancestors + Maybe<wr::WrSpaceAndClip> ancestorSpaceAndClip = + DefineScrollLayers(aASR->mParent, aItem); + + Maybe<ScrollMetadata> metadata = + aASR->mScrollableFrame->ComputeScrollMetadata( + mManager, aItem->ReferenceFrame(), Nothing(), nullptr); + if (!metadata) { + MOZ_ASSERT_UNREACHABLE("Expected scroll metadata to be available!"); + return ancestorSpaceAndClip; + } + + FrameMetrics& metrics = metadata->GetMetrics(); + if (!metrics.IsScrollable()) { + // This item is a scrolling no-op, skip over it in the ASR chain. + return ancestorSpaceAndClip; + } + + nsIScrollableFrame* scrollableFrame = aASR->mScrollableFrame; + nsIFrame* scrollFrame = do_QueryFrame(scrollableFrame); + nsPoint offset = scrollFrame->GetOffsetToCrossDoc(aItem->ReferenceFrame()); + float 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::WrSpaceAndClip> parent = ancestorSpaceAndClip; + if (parent) { + parent->space = SpatialIdAfterOverride(parent->space); + } + // 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. + LayoutDevicePoint scrollOffset = LayoutDevicePoint::FromAppUnitsRounded( + scrollableFrame->GetScrollPosition(), auPerDevPixel); + + return Some(mBuilder->DefineScrollLayer( + viewId, parent, wr::ToLayoutRect(contentRect), + wr::ToLayoutRect(clipBounds), wr::ToLayoutPoint(scrollOffset))); +} + +Maybe<wr::WrClipChainId> ClipManager::DefineClipChain( + const DisplayItemClipChain* aChain, int32_t aAppUnitsPerDevPixel) { + AutoTArray<wr::WrClipId, 6> clipIds; + // 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) { + ClipIdMap& cache = mCacheStack.top(); + auto it = cache.find(chain); + if (it != cache.end()) { + // Found it in the currently-active cache, so just use the id we have for + // it. + CLIP_LOG("cache[%p] => %zu\n", chain, it->second.id); + clipIds.AppendElement(it->second); + continue; + } + if (!chain->mClip.HasClip()) { + // This item in the chain is a no-op, skip over it + continue; + } + + LayoutDeviceRect clip = LayoutDeviceRect::FromAppUnits( + chain->mClip.GetClipRect(), aAppUnitsPerDevPixel); + nsTArray<wr::ComplexClipRegion> wrRoundedRects; + chain->mClip.ToComplexClipRegions(aAppUnitsPerDevPixel, wrRoundedRects); + + Maybe<wr::WrSpaceAndClip> spaceAndClip = GetScrollLayer(chain->mASR); + // Before calling DefineClipChain we defined the ASRs by calling + // DefineScrollLayers, so we must have a scrollId here. + MOZ_ASSERT(spaceAndClip.isSome()); + + // Define the clip + spaceAndClip->space = SpatialIdAfterOverride(spaceAndClip->space); + wr::WrClipId clipId = mBuilder->DefineClip( + spaceAndClip, wr::ToLayoutRect(clip), &wrRoundedRects); + clipIds.AppendElement(clipId); + cache[chain] = clipId; + CLIP_LOG("cache[%p] <= %zu\n", chain, clipId.id); + } + + if (clipIds.IsEmpty()) { + return Nothing(); + } + + return Some(mBuilder->DefineClipChain(clipIds)); +} + +ClipManager::~ClipManager() { + MOZ_ASSERT(!mBuilder); + MOZ_ASSERT(mCacheStack.empty()); + MOZ_ASSERT(mItemClipStack.empty()); +} + +ClipManager::ItemClips::ItemClips(const ActiveScrolledRoot* aASR, + const DisplayItemClipChain* aChain, + bool aSeparateLeaf) + : mASR(aASR), mChain(aChain), 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) { + return mASR == aOther.mASR && mChain == aOther.mChain && + mSeparateLeaf == aOther.mSeparateLeaf; +} + +void ClipManager::ItemClips::CopyOutputsFrom(const ItemClips& aOther) { + mScrollId = aOther.mScrollId; + mClipChainId = aOther.mClipChainId; +} + +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..c1bec5aa6c --- /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" + +class nsDisplayItem; + +namespace mozilla { + +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(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); + + Maybe<wr::WrSpaceAndClip> GetScrollLayer(const ActiveScrolledRoot* aASR); + + Maybe<wr::WrSpaceAndClip> 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. + typedef std::unordered_map<const DisplayItemClipChain*, wr::WrClipId> + ClipIdMap; + 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, bool aSeparateLeaf); + + // These are the "inputs" - they come from the nsDisplayItem + const ActiveScrolledRoot* mASR; + const DisplayItemClipChain* mChain; + 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); + void CopyOutputsFrom(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..212a0449eb --- /dev/null +++ b/gfx/layers/wr/DisplayItemCache.cpp @@ -0,0 +1,197 @@ +/* -*- 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 (!(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..e7a1b83e81 --- /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" + +class nsDisplayList; +class nsDisplayListBuilder; +class nsPaintedDisplayItem; + +namespace mozilla { + +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 is not meant to be flipped in the middle of a display list build, + * but rather set before the display list build starts to suppress use of the + * cache for that display list build. + */ + 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/IpcResourceUpdateQueue.cpp b/gfx/layers/wr/IpcResourceUpdateQueue.cpp new file mode 100644 index 0000000000..d69d5269e4 --- /dev/null +++ b/gfx/layers/wr/IpcResourceUpdateQueue.cpp @@ -0,0 +1,498 @@ +/* -*- 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; + auto shmType = ipc::SharedMemory::SharedMemoryType::TYPE_BASIC; + if (!mShmAllocator->AllocShmem(aSize, shmType, &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::AddPrivateExternalImage( + wr::ExternalImageId aExtId, wr::ImageKey aKey, wr::ImageDescriptor aDesc) { + mUpdates.AppendElement( + layers::OpAddPrivateExternalImage(aExtId, aKey, aDesc)); +} + +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, nullptr, 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::UpdatePrivateExternalImage( + wr::ExternalImageId aExtId, wr::ImageKey aKey, + const wr::ImageDescriptor& aDesc, ImageIntRect aDirtyRect) { + mUpdates.AppendElement( + layers::OpUpdatePrivateExternalImage(aExtId, aKey, aDesc, aDirtyRect)); +} + +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..0b3f890cbe --- /dev/null +++ b/gfx/layers/wr/IpcResourceUpdateQueue.h @@ -0,0 +1,201 @@ +/* -*- 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 AddPrivateExternalImage(wr::ExternalImageId aExtId, wr::ImageKey aKey, + wr::ImageDescriptor aDesc); + + 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 UpdatePrivateExternalImage(wr::ExternalImageId aExtId, wr::ImageKey aKey, + const wr::ImageDescriptor& aDesc, + 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..a928b2a15a --- /dev/null +++ b/gfx/layers/wr/OMTAController.cpp @@ -0,0 +1,40 @@ +/* -*- 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" + +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..f2979cf97b --- /dev/null +++ b/gfx/layers/wr/OMTASampler.cpp @@ -0,0 +1,243 @@ +/* -*- 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") { + 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()); + + 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); + + // We do this even if the arrays are empty, because it will clear out any + // previous properties store on the WR side, which is desirable. + aTxn.UpdateDynamicProperties(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..860a84acbd --- /dev/null +++ b/gfx/layers/wr/OMTASampler.h @@ -0,0 +1,151 @@ +/* -*- 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/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; + + 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; + + // 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; + static StaticAutoPtr<std::unordered_map<uint64_t, RefPtr<OMTASampler>>> + sWindowIdMap; + Maybe<wr::WrWindowId> mWindowId; + + // Lock used to protected mSamplerThreadId + mutable Mutex mThreadIdLock; + // 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; + // 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; +}; + +} // 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..0168725eef --- /dev/null +++ b/gfx/layers/wr/RenderRootStateManager.cpp @@ -0,0 +1,213 @@ +/* -*- 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::AddPipelineIdForAsyncCompositable( + const wr::PipelineId& aPipelineId, const CompositableHandle& aHandle) { + WrBridge()->AddPipelineIdForAsyncCompositable(aPipelineId, aHandle); +} +void RenderRootStateManager::AddPipelineIdForCompositable( + const wr::PipelineId& aPipelineId, const CompositableHandle& aHandle) { + WrBridge()->AddPipelineIdForCompositable(aPipelineId, aHandle); +} +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..befe169271 --- /dev/null +++ b/gfx/layers/wr/RenderRootStateManager.h @@ -0,0 +1,100 @@ +/* -*- 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" + +namespace mozilla { + +namespace layers { + +class RenderRootStateManager { + typedef nsTHashtable<nsRefPtrHashKey<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 AddPipelineIdForAsyncCompositable(const wr::PipelineId& aPipelineId, + const CompositableHandle& aHandlee); + void AddPipelineIdForCompositable(const wr::PipelineId& aPipelineId, + const CompositableHandle& aHandlee); + 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 = nullptr); + Maybe<wr::FontKey> GetFontKeyForUnscaledFont( + gfx::UnscaledFont* aUnscaledFont, + wr::IpcResourceUpdateQueue* aResources = nullptr); + + 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..e9c964e023 --- /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::Message* aMsg, IProtocol* aActor, paramType&& aParam) { + WriteIPDLParam(aMsg, aActor, aParam.mIdNamespace); + WriteIPDLParam(aMsg, aActor, aParam.mRect); + WriteIPDLParam(aMsg, aActor, aParam.mCommands); + WriteIPDLParam(aMsg, aActor, std::move(aParam.mDL)); + WriteIPDLParam(aMsg, aActor, aParam.mDLDesc); + WriteIPDLParam(aMsg, aActor, aParam.mRemotePipelineIds); + WriteIPDLParam(aMsg, aActor, aParam.mResourceUpdates); + WriteIPDLParam(aMsg, aActor, aParam.mSmallShmems); + WriteIPDLParam(aMsg, aActor, std::move(aParam.mLargeShmems)); + WriteIPDLParam(aMsg, aActor, aParam.mScrollData); +} + +bool IPDLParamTraits<mozilla::layers::DisplayListData>::Read( + const IPC::Message* aMsg, PickleIterator* aIter, IProtocol* aActor, + paramType* aResult) { + if (ReadIPDLParam(aMsg, aIter, aActor, &aResult->mIdNamespace) && + ReadIPDLParam(aMsg, aIter, aActor, &aResult->mRect) && + ReadIPDLParam(aMsg, aIter, aActor, &aResult->mCommands) && + ReadIPDLParam(aMsg, aIter, aActor, &aResult->mDL) && + ReadIPDLParam(aMsg, aIter, aActor, &aResult->mDLDesc) && + ReadIPDLParam(aMsg, aIter, aActor, &aResult->mRemotePipelineIds) && + ReadIPDLParam(aMsg, aIter, aActor, &aResult->mResourceUpdates) && + ReadIPDLParam(aMsg, aIter, aActor, &aResult->mSmallShmems) && + ReadIPDLParam(aMsg, aIter, aActor, &aResult->mLargeShmems) && + ReadIPDLParam(aMsg, aIter, aActor, &aResult->mScrollData)) { + return true; + } + return false; +} + +void WriteScrollUpdates(IPC::Message* aMsg, 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(aMsg, aActor, aParam.Count()); + for (auto it = aParam.Iter(); !it.Done(); it.Next()) { + WriteIPDLParam(aMsg, aActor, it.Key()); + WriteIPDLParam(aMsg, aActor, it.Data()); + } +} + +bool ReadScrollUpdates(const IPC::Message* aMsg, PickleIterator* aIter, + IProtocol* aActor, layers::ScrollUpdatesMap* aResult) { + // Manually deserialize mScrollUpdates as a stream of K,V pairs + uint32_t count; + if (!ReadIPDLParam(aMsg, aIter, 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(aMsg, aIter, aActor, &key) || + !ReadIPDLParam(aMsg, aIter, aActor, &data)) { + return false; + } + map.Put(key, std::move(data)); + } + + MOZ_RELEASE_ASSERT(map.Count() == count); + *aResult = std::move(map); + return true; +} + +void IPDLParamTraits<mozilla::layers::TransactionData>::Write( + IPC::Message* aMsg, IProtocol* aActor, paramType&& aParam) { + WriteIPDLParam(aMsg, aActor, aParam.mIdNamespace); + WriteIPDLParam(aMsg, aActor, aParam.mCommands); + WriteIPDLParam(aMsg, aActor, aParam.mResourceUpdates); + WriteIPDLParam(aMsg, aActor, aParam.mSmallShmems); + WriteIPDLParam(aMsg, aActor, std::move(aParam.mLargeShmems)); + WriteScrollUpdates(aMsg, aActor, aParam.mScrollUpdates); + WriteIPDLParam(aMsg, aActor, aParam.mPaintSequenceNumber); +} + +bool IPDLParamTraits<mozilla::layers::TransactionData>::Read( + const IPC::Message* aMsg, PickleIterator* aIter, IProtocol* aActor, + paramType* aResult) { + if (ReadIPDLParam(aMsg, aIter, aActor, &aResult->mIdNamespace) && + ReadIPDLParam(aMsg, aIter, aActor, &aResult->mCommands) && + ReadIPDLParam(aMsg, aIter, aActor, &aResult->mResourceUpdates) && + ReadIPDLParam(aMsg, aIter, aActor, &aResult->mSmallShmems) && + ReadIPDLParam(aMsg, aIter, aActor, &aResult->mLargeShmems) && + ReadScrollUpdates(aMsg, aIter, aActor, &aResult->mScrollUpdates) && + ReadIPDLParam(aMsg, aIter, 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..1f112bba92 --- /dev/null +++ b/gfx/layers/wr/RenderRootTypes.h @@ -0,0 +1,71 @@ +/* -*- 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> mDL; + wr::BuiltDisplayListDescriptor mDLDesc; + nsTArray<wr::PipelineId> mRemotePipelineIds; + 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::Message* aMsg, IProtocol* aActor, paramType&& aParam); + + static bool Read(const IPC::Message* aMsg, PickleIterator* aIter, + IProtocol* aActor, paramType* aResult); +}; + +template <> +struct IPDLParamTraits<mozilla::layers::TransactionData> { + typedef mozilla::layers::TransactionData paramType; + + static void Write(IPC::Message* aMsg, IProtocol* aActor, paramType&& aParam); + + static bool Read(const IPC::Message* aMsg, PickleIterator* aIter, + 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..15645d2539 --- /dev/null +++ b/gfx/layers/wr/StackingContextHelper.cpp @@ -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/. */ + +#include "mozilla/layers/StackingContextHelper.h" + +#include "mozilla/PresShell.h" +#include "UnitTransforms.h" +#include "nsDisplayList.h" + +namespace mozilla { +namespace layers { + +StackingContextHelper::StackingContextHelper() + : mBuilder(nullptr), + mScale(1.0f, 1.0f), + mAffectsClipPositioning(false), + mRasterizeLocally(false) { + // mOrigin remains at 0,0 +} + +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) { + 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 = FrameLayerBuilder::ChooseScale( + aContainerFrame, aContainerItem, r, aParentSC.mScale.width, + aParentSC.mScale.height, mInheritedTransform, + /* aCanDraw2D = */ true); + } else { + mScale = gfx::Size(1.0f, 1.0f); + mInheritedTransform = gfx::Matrix::Scaling(1.f, 1.f); + } + + if (aParams.mAnimated) { + mSnappingSurfaceTransform = + gfx::Matrix::Scaling(mScale.width, mScale.height); + } else { + mSnappingSurfaceTransform = + transform2d * aParentSC.mSnappingSurfaceTransform; + } + + } else if (aParams.reference_frame_kind == wr::WrReferenceFrameKind::Zoom && + aContainerItem && + aContainerItem->GetType() == DisplayItemType::TYPE_ASYNC_ZOOM && + aContainerItem->Frame()) { + double resolution = aContainerItem->Frame()->PresShell()->GetResolution(); + gfx::Matrix transform = gfx::Matrix::Scaling(resolution, resolution); + + mInheritedTransform = transform * aParentSC.mInheritedTransform; + mScale = resolution * aParentSC.mScale; + + MOZ_ASSERT(!aParams.mAnimated); + mSnappingSurfaceTransform = transform * aParentSC.mSnappingSurfaceTransform; + + } else { + mInheritedTransform = aParentSC.mInheritedTransform; + mScale = aParentSC.mScale; + } + + auto rasterSpace = + mRasterizeLocally + ? wr::RasterSpace::Local(std::max(mScale.width, mScale.height)) + : 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()); + } +} + +const Maybe<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..bd9e576197 --- /dev/null +++ b/gfx/layers/wr/StackingContextHelper.h @@ -0,0 +1,130 @@ +/* -*- 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" + +class nsDisplayTransform; + +namespace mozilla { + +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::Size GetInheritedScale() const { return mScale; } + + const gfx::Matrix& GetInheritedTransform() const { + return mInheritedTransform; + } + + const gfx::Matrix& GetSnappingSurfaceTransform() const { + return mSnappingSurfaceTransform; + } + + const Maybe<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::Size 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 Nothing(), 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. + Maybe<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..61a31e6f3d --- /dev/null +++ b/gfx/layers/wr/WebRenderBridgeChild.cpp @@ -0,0 +1,615 @@ +/* -*- 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" + +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(). + // mActiveResourceTracker is not cleared here, since it is + // used by PersistentBufferProviderShared. + 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(mParentCommands); + mParentCommands.Clear(); + } +} + +void WebRenderBridgeChild::AddPipelineIdForAsyncCompositable( + const wr::PipelineId& aPipelineId, const CompositableHandle& aHandle) { + AddWebRenderParentCommand( + OpAddPipelineIdForCompositable(aPipelineId, aHandle, /* isAsync */ true)); +} + +void WebRenderBridgeChild::AddPipelineIdForCompositable( + const wr::PipelineId& aPipelineId, const CompositableHandle& aHandle) { + AddWebRenderParentCommand(OpAddPipelineIdForCompositable( + aPipelineId, aHandle, /* isAsync */ false)); +} + +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, 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); + 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()); + + wr::FontInstanceKey instanceKey = {wr::IdNamespace{0}, 0}; + if (mFontInstanceKeys.Get(aScaledFont, &instanceKey)) { + return Some(instanceKey); + } + + Maybe<wr::IpcResourceUpdateQueue> resources = + aResources ? Nothing() : Some(wr::IpcResourceUpdateQueue(this)); + aResources = resources.ptrOr(aResources); + + Maybe<wr::FontKey> fontKey = + GetFontKeyForUnscaledFont(aScaledFont->GetUnscaledFont(), aResources); + if (fontKey.isNothing()) { + return Nothing(); + } + + 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())); + if (resources.isSome()) { + UpdateResources(resources.ref()); + } + + mFontInstanceKeys.Put(aScaledFont, instanceKey); + + return Some(instanceKey); +} + +Maybe<wr::FontKey> WebRenderBridgeChild::GetFontKeyForUnscaledFont( + gfx::UnscaledFont* aUnscaled, wr::IpcResourceUpdateQueue* aResources) { + MOZ_ASSERT(!mDestroyed); + + wr::FontKey fontKey = {wr::IdNamespace{0}, 0}; + if (!mFontKeys.Get(aUnscaled, &fontKey)) { + Maybe<wr::IpcResourceUpdateQueue> resources = + aResources ? Nothing() : Some(wr::IpcResourceUpdateQueue(this)); + + FontFileDataSink sink = {&fontKey, this, resources.ptrOr(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(); + } + + if (resources.isSome()) { + UpdateResources(resources.ref()); + } + + mFontKeys.Put(aUnscaled, fontKey); + } + + return Some(fontKey); +} + +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() { + if (!IPCOpen()) { + return; + } + SendSyncWithCompositor(); +} + +void WebRenderBridgeChild::Connect(CompositableClient* aCompositable, + ImageContainer* aImageContainer) { + MOZ_ASSERT(!mDestroyed); + MOZ_ASSERT(aCompositable); + + static uint64_t sNextID = 1; + uint64_t id = sNextID++; + + mCompositables.Put(id, aCompositable); + + CompositableHandle handle(id); + aCompositable->InitIPDL(handle); + SendNewCompositable(handle, aCompositable->GetTextureInfo()); +} + +void WebRenderBridgeChild::UseTiledLayerBuffer( + CompositableClient* aCompositable, + const SurfaceDescriptorTiles& aTiledDescriptor) {} + +void WebRenderBridgeChild::UpdateTextureRegion( + CompositableClient* aCompositable, + const ThebesBufferData& aThebesBufferData, + const nsIntRegion& aUpdatedRegion) {} + +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(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(nullptr, 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(nullptr, t.mTextureClient->GetIPDLActor(), t.mTimeStamp, + t.mPictureRect, t.mFrameID, t.mProducerID, readLocked)); + GetCompositorBridgeChild()->HoldUntilCompositableRefReleasedIfNecessary( + t.mTextureClient); + + auto fenceFd = t.mTextureClient->GetInternalData()->GetAcquireFence(); + if (fenceFd.IsValid()) { + AddWebRenderParentCommand(CompositableOperation( + aCompositable->GetIPCHandle(), + OpDeliverAcquireFence(nullptr, t.mTextureClient->GetIPDLActor(), + fenceFd))); + } + } + AddWebRenderParentCommand(CompositableOperation(aCompositable->GetIPCHandle(), + OpUseTexture(textures))); +} + +void WebRenderBridgeChild::UseComponentAlphaTextures( + CompositableClient* aCompositable, TextureClient* aClientOnBlack, + TextureClient* aClientOnWhite) {} + +void WebRenderBridgeChild::UpdateFwdTransactionId() { + GetCompositorBridgeChild()->UpdateFwdTransactionId(); +} + +uint64_t WebRenderBridgeChild::GetFwdTransactionId() { + return GetCompositorBridgeChild()->GetFwdTransactionId(); +} + +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); + mManager = aManager; + + MOZ_ASSERT(NS_IsMainThread() || !XRE_IsContentProcess()); + mActiveResourceTracker = + MakeUnique<ActiveResourceTracker>(1000, "CompositableForwarder", nullptr); +} + +ipc::IShmemAllocator* WebRenderBridgeChild::GetShmemAllocator() { + if (!IPCOpen()) { + return nullptr; + } + return static_cast<CompositorBridgeChild*>(Manager()); +} + +RefPtr<KnowsCompositor> WebRenderBridgeChild::GetForMedia() { + MOZ_ASSERT(NS_IsMainThread()); + + // Ensure devices initialization for video playback. The devices are lazily + // initialized with WebRender to reduce memory usage. + 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::ToggleCaptureSequence() { + this->SendToggleCaptureSequence(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/wr/WebRenderBridgeChild.h b/gfx/layers/wr/WebRenderBridgeChild.h new file mode 100644 index 0000000000..672cd68055 --- /dev/null +++ b/gfx/layers/wr/WebRenderBridgeChild.h @@ -0,0 +1,262 @@ +/* -*- 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() override; + ActiveResourceTracker* GetActiveResourceTracker() override { + return mActiveResourceTracker.get(); + } + + void AddPipelineIdForAsyncCompositable(const wr::PipelineId& aPipelineId, + const CompositableHandle& aHandlee); + void AddPipelineIdForCompositable(const wr::PipelineId& aPipelineId, + const CompositableHandle& aHandlee); + 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; + } + + 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, + 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 = nullptr); + Maybe<wr::FontKey> GetFontKeyForUnscaledFont( + gfx::UnscaledFont* aUnscaledFont, + wr::IpcResourceUpdateQueue* aResources = nullptr); + 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 ToggleCaptureSequence(); + + private: + friend class CompositorBridgeChild; + + ~WebRenderBridgeChild(); + + wr::ExternalImageId GetNextExternalImageId(); + + // CompositableForwarder + void Connect(CompositableClient* aCompositable, + ImageContainer* aImageContainer = nullptr) override; + void UseTiledLayerBuffer( + CompositableClient* aCompositable, + const SurfaceDescriptorTiles& aTiledDescriptor) override; + void UpdateTextureRegion(CompositableClient* aCompositable, + const ThebesBufferData& aThebesBufferData, + const nsIntRegion& aUpdatedRegion) 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 UseComponentAlphaTextures(CompositableClient* aCompositable, + TextureClient* aClientOnBlack, + TextureClient* aClientOnWhite) override; + void UpdateFwdTransactionId() override; + uint64_t GetFwdTransactionId() 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; + nsDataHashtable<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; + nsDataHashtable<UnscaledFontHashKey, wr::FontKey> mFontKeys; + + uint32_t mFontInstanceKeysDeleted; + nsDataHashtable<ScaledFontHashKey, wr::FontInstanceKey> mFontInstanceKeys; + + UniquePtr<ActiveResourceTracker> mActiveResourceTracker; + + 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..1484cc9976 --- /dev/null +++ b/gfx/layers/wr/WebRenderBridgeParent.cpp @@ -0,0 +1,2596 @@ +/* -*- 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 "Layers.h" +#include "nsExceptionHandler.h" +#include "mozilla/Range.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/gfx/gfxVars.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/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/Telemetry.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Unused.h" +#include "mozilla/webrender/RenderThread.h" +#include "mozilla/widget/CompositorWidget.h" + +#ifdef XP_WIN +# include "mozilla/widget/WinCompositorWidget.h" +#endif + +#ifdef MOZ_GECKO_PROFILER +# include "mozilla/ProfilerMarkerTypes.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(); +} + +void gecko_profiler_start_marker(const char* name) { + PROFILER_MARKER(mozilla::ProfilerString8View::WrapNullTerminatedString(name), + GRAPHICS, mozilla::MarkerTiming::IntervalStart(), Tracing, + "WebRender"); +} + +void gecko_profiler_end_marker(const char* name) { + PROFILER_MARKER(mozilla::ProfilerString8View::WrapNullTerminatedString(name), + GRAPHICS, mozilla::MarkerTiming::IntervalEnd(), Tracing, + "WebRender"); +} + +void gecko_profiler_event_marker(const char* name) { + PROFILER_MARKER(mozilla::ProfilerString8View::WrapNullTerminatedString(name), + GRAPHICS, {}, Tracing, "WebRender"); +} + +void gecko_profiler_add_text_marker(const char* name, const char* text_bytes, + size_t text_len, uint64_t microseconds) { +#ifdef MOZ_GECKO_PROFILER + if (profiler_thread_is_being_profiled()) { + auto now = mozilla::TimeStamp::NowUnfuzzed(); + auto start = now - mozilla::TimeDuration::FromMicroseconds(microseconds); + PROFILER_MARKER_TEXT( + mozilla::ProfilerString8View::WrapNullTerminatedString(name), GRAPHICS, + mozilla::MarkerTiming::Interval(start, now), + mozilla::ProfilerString8View(text_bytes, text_len)); + } +#endif +} + +bool gecko_profiler_thread_is_being_profiled() { +#ifdef MOZ_GECKO_PROFILER + return profiler_thread_is_being_profiled(); +#else + return false; +#endif +} + +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); +} + +void gecko_profiler_register_thread(const char* name) { + PROFILER_REGISTER_THREAD(name); +} + +void gecko_profiler_unregister_thread() { PROFILER_UNREGISTER_THREAD(); } + +void record_telemetry_time(mozilla::wr::TelemetryProbe aProbe, + uint64_t aTimeNs) { + uint32_t time_ms = (uint32_t)(aTimeNs / 1000000); + switch (aProbe) { + case mozilla::wr::TelemetryProbe::SceneBuildTime: + mozilla::Telemetry::Accumulate(mozilla::Telemetry::WR_SCENEBUILD_TIME, + time_ms); + break; + case mozilla::wr::TelemetryProbe::SceneSwapTime: + mozilla::Telemetry::Accumulate(mozilla::Telemetry::WR_SCENESWAP_TIME, + time_ms); + break; + case mozilla::wr::TelemetryProbe::FrameBuildTime: + mozilla::Telemetry::Accumulate(mozilla::Telemetry::WR_FRAMEBUILD_TIME, + time_ms); + break; + default: + MOZ_ASSERT(false); + break; + } +} + +static CrashReporter::Annotation FromWrCrashAnnotation( + mozilla::wr::CrashAnnotation aAnnotation) { + switch (aAnnotation) { + case mozilla::wr::CrashAnnotation::CompileShader: + return CrashReporter::Annotation::GraphicsCompileShader; + 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 { + +namespace layers { + +using namespace mozilla::gfx; + +class ScheduleObserveLayersUpdate : public wr::NotificationHandler { + public: + ScheduleObserveLayersUpdate(RefPtr<CompositorBridgeParentBase> aBridge, + LayersId aLayersId, LayersObserverEpoch aEpoch, + bool aIsActive) + : mBridge(aBridge), + mLayersId(aLayersId), + mObserverEpoch(aEpoch), + mIsActive(aIsActive) {} + + void Notify(wr::Checkpoint) override { + CompositorThread()->Dispatch( + NewRunnableMethod<LayersId, LayersObserverEpoch, int>( + "ObserveLayersUpdate", mBridge, + &CompositorBridgeParentBase::ObserveLayersUpdate, mLayersId, + mObserverEpoch, mIsActive)); + } + + protected: + RefPtr<CompositorBridgeParentBase> mBridge; + LayersId mLayersId; + LayersObserverEpoch mObserverEpoch; + 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(); +#ifdef MOZ_GECKO_PROFILER + if (profiler_can_accept_markers()) { + profiler_add_marker("CONTENT_FULL_PAINT_TIME", + geckoprofiler::category::GRAPHICS, + MarkerTiming::Interval(startTime, endTime), + baseprofiler::markers::ContentBuildMarker{}); + } +#endif + Telemetry::Accumulate( + Telemetry::CONTENT_FULL_PAINT_TIME, + static_cast<uint32_t>((endTime - startTime).ToMilliseconds())); + 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) {} + + void Add(const wr::ImageKey& aKey, const wr::ExternalImageId& aId) { + mSurfaces.AppendElement(wr::ExternalImageKeyPair{aKey, aId}); + } + + void Notify(wr::Checkpoint) override { + CompositorThread()->Dispatch( + NewRunnableMethod<nsTArray<wr::ExternalImageKeyPair>>( + "ObserveSharedSurfaceRelease", mWrBridge, + &WebRenderBridgeParent::ObserveSharedSurfaceRelease, + std::move(mSurfaces))); + } + + private: + 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), + mChildLayersObserverEpoch{0}, + mParentLayersObserverEpoch{0}, + mWrEpoch{0}, + mIdNamespace(aApi->GetNamespace()), +#if defined(MOZ_WIDGET_ANDROID) + mScreenPixelsTarget(nullptr), +#endif + mPaused(false), + mDestroyed(false), + mReceivedDisplayList(false), + mIsFirstPaint(true), + mSkippedComposite(false), + mDisablingNativeCompositor(false), + mPendingScrollPayloads("WebRenderBridgeParent::mPendingScrollPayloads") { + MOZ_ASSERT(mAsyncImageManager); + mAsyncImageManager->AddPipeline(mPipelineId, this); + if (IsRootWebRenderBridgeParent()) { + MOZ_ASSERT(!mCompositorScheduler); + mCompositorScheduler = new CompositorVsyncScheduler(this, mWidget); + } + UpdateDebugFlags(); + UpdateQualitySettings(); + UpdateProfilerUI(); +} + +WebRenderBridgeParent::WebRenderBridgeParent(const wr::PipelineId& aPipelineId, + nsCString&& aError) + : mCompositorBridge(nullptr), + mPipelineId(aPipelineId), + mChildLayersObserverEpoch{0}, + mParentLayersObserverEpoch{0}, + mWrEpoch{0}, + mIdNamespace{0}, + mInitError(aError), + mPaused(false), + mDestroyed(true), + mReceivedDisplayList(false), + mIsFirstPaint(false), + mSkippedComposite(false), + mDisablingNativeCompositor(false), + mPendingScrollPayloads("WebRenderBridgeParent::mPendingScrollPayloads") {} + +WebRenderBridgeParent::~WebRenderBridgeParent() {} + +/* 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; + } + mDestroyed = true; + if (mWebRenderBridgeRef) { + // Break mutual reference + mWebRenderBridgeRef->Clear(); + mWebRenderBridgeRef = nullptr; + } + 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()) { + 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; + + 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)) { + return false; + } + aUpdates.AddImage(op.key(), op.descriptor(), bytes); + 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)) { + return false; + } + aUpdates.UpdateImageBuffer(op.key(), op.descriptor(), bytes); + 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)) { + return false; + } + aUpdates.AddBlobImage(op.key(), op.descriptor(), bytes, + wr::ToDeviceIntRect(op.visibleRect())); + 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)) { + return false; + } + aUpdates.UpdateBlobImage(op.key(), op.descriptor(), bytes, + wr::ToDeviceIntRect(op.visibleRect()), + wr::ToLayoutIntRect(op.dirtyRect())); + 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::TOpAddPrivateExternalImage: { + const auto& op = cmd.get_OpAddPrivateExternalImage(); + if (!AddPrivateExternalImage(op.externalImageId(), op.key(), + op.descriptor(), aUpdates)) { + return false; + } + break; + } + case OpUpdateResource::TOpAddSharedExternalImage: { + const auto& op = cmd.get_OpAddSharedExternalImage(); + if (!AddSharedExternalImage(op.externalImageId(), op.key(), aUpdates)) { + return false; + } + break; + } + case OpUpdateResource::TOpPushExternalImageForTexture: { + const auto& op = cmd.get_OpPushExternalImageForTexture(); + CompositableTextureHostRef texture; + texture = TextureHost::AsTextureHost(op.textureParent()); + if (!PushExternalImageForTexture(op.externalImageId(), op.key(), + texture, op.isUpdate(), aUpdates)) { + return false; + } + break; + } + case OpUpdateResource::TOpUpdatePrivateExternalImage: { + const auto& op = cmd.get_OpUpdatePrivateExternalImage(); + if (!UpdatePrivateExternalImage(op.externalImageId(), op.key(), + op.descriptor(), op.dirtyRect(), + aUpdates)) { + return false; + } + break; + } + case OpUpdateResource::TOpUpdateSharedExternalImage: { + const auto& op = cmd.get_OpUpdateSharedExternalImage(); + if (!UpdateSharedExternalImage(op.externalImageId(), op.key(), + op.dirtyRect(), aUpdates, + scheduleRelease)) { + return false; + } + break; + } + case OpUpdateResource::TOpAddRawFont: { + if (!ReadRawFont(cmd.get_OpAddRawFont(), reader, aUpdates)) { + return 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)) { + return false; + } + aUpdates.AddFontDescriptor(op.key(), bytes, op.fontIndex()); + 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)) { + return false; + } + aUpdates.AddFontInstance(op.instanceKey(), op.fontKey(), op.glyphSize(), + op.options().ptrOr(nullptr), + op.platformOptions().ptrOr(nullptr), + variations); + 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)); + } + return true; +} + +bool WebRenderBridgeParent::AddPrivateExternalImage( + wr::ExternalImageId aExtId, wr::ImageKey aKey, wr::ImageDescriptor aDesc, + wr::TransactionBuilder& aResources) { + if (!MatchesNamespace(aKey)) { + MOZ_ASSERT_UNREACHABLE("Stale private external image key (add)!"); + return true; + } + + aResources.AddExternalImage(aKey, aDesc, aExtId, + wr::ExternalImageType::Buffer(), 0); + + return true; +} + +bool WebRenderBridgeParent::UpdatePrivateExternalImage( + wr::ExternalImageId aExtId, wr::ImageKey aKey, + const wr::ImageDescriptor& aDesc, const ImageIntRect& aDirtyRect, + wr::TransactionBuilder& aResources) { + if (!MatchesNamespace(aKey)) { + MOZ_ASSERT_UNREACHABLE("Stale private external image key (update)!"); + return true; + } + + aResources.UpdateExternalImageWithDirtyRect( + aKey, aDesc, aExtId, wr::ExternalImageType::Buffer(), + wr::ToDeviceIntRect(aDirtyRect), 0); + + return true; +} + +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)); + + auto imageType = + mApi->GetBackendType() == WebRenderBackend::SOFTWARE + ? wr::ExternalImageType::TextureHandle(wr::ImageBufferKind::Texture2D) + : wr::ExternalImageType::Buffer(); + wr::ImageDescriptor descriptor(dSurf->GetSize(), 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; + } + + auto imageType = + mApi->GetBackendType() == WebRenderBackend::SOFTWARE + ? wr::ExternalImageType::TextureHandle(wr::ImageBufferKind::Texture2D) + : wr::ExternalImageType::Buffer(); + wr::ImageDescriptor descriptor(dSurf->GetSize(), dSurf->Stride(), + dSurf->GetFormat()); + aResources.UpdateExternalImageWithDirtyRect( + aKey, descriptor, aExtId, imageType, wr::ToDeviceIntRect(aDirtyRect), 0); + + return true; +} + +void WebRenderBridgeParent::ObserveSharedSurfaceRelease( + const nsTArray<wr::ExternalImageKeyPair>& aPairs) { + if (!mDestroyed) { + Unused << SendWrReleasedImages(aPairs); + } + 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) { + if (mDestroyed || aIdNamespace != mIdNamespace) { + wr::IpcResourceUpdateQueue::ReleaseShmems(this, aSmallShmems); + wr::IpcResourceUpdateQueue::ReleaseShmems(this, aLargeShmems); + return IPC_OK(); + } + + wr::TransactionBuilder txn; + 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(); + } 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 (!success) { + return IPC_FAIL(this, "Invalid WebRender resource data shmem or address."); + } + + mApi->SendTransaction(txn); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult WebRenderBridgeParent::RecvDeleteCompositorAnimations( + nsTArray<uint64_t>&& aIds) { + if (mDestroyed) { + return IPC_OK(); + } + + // 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::WriteCollectedFramesPromise> +WebRenderBridgeParent::WriteCollectedFrames() { + return mApi->WriteCollectedFrames(); +} + +RefPtr<wr::WebRenderAPI::GetCollectedFramesPromise> +WebRenderBridgeParent::GetCollectedFrames() { + return mApi->GetCollectedFrames(); +} + +void WebRenderBridgeParent::AddPendingScrollPayload( + CompositionPayload& aPayload, const VsyncId& aCompositeStartId) { + auto pendingScrollPayloads = mPendingScrollPayloads.Lock(); + nsTArray<CompositionPayload>* payloads = + pendingScrollPayloads->LookupOrAdd(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&& aDL, + 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 aObserveLayersUpdate) { + if (NS_WARN_IF(!UpdateResources(aResourceUpdates, aSmallShmems, aLargeShmems, + aTxn))) { + return false; + } + + wr::Vec<uint8_t> dlData(std::move(aDL)); + + if (IsRootWebRenderBridgeParent()) { + LayoutDeviceIntSize widgetSize = mWidget->GetClientSize(); + LayoutDeviceIntRect rect = + LayoutDeviceIntRect(LayoutDeviceIntPoint(), widgetSize); + aTxn.SetDocumentView(rect); + } + gfx::DeviceColor clearColor(0.f, 0.f, 0.f, 0.f); + aTxn.SetDisplayList(clearColor, aWrEpoch, + wr::ToLayoutSize(RoundedToInt(aRect).Size()), mPipelineId, + aDLDesc, dlData); + + if (aObserveLayersUpdate) { + aTxn.Notify( + wr::Checkpoint::SceneBuilt, + MakeUnique<ScheduleObserveLayersUpdate>( + mCompositorBridge, GetLayersId(), mChildLayersObserverEpoch, 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 true; +} + +bool WebRenderBridgeParent::ProcessDisplayListData( + DisplayListData& aDisplayList, wr::Epoch aWrEpoch, + const TimeStamp& aTxnStartTime, bool aValidTransaction, + bool aObserveLayersUpdate) { + wr::TransactionBuilder txn; + Maybe<wr::AutoTransactionSender> sender; + + // 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()); + if (aValidTransaction) { + MOZ_ASSERT(aDisplayList.mIdNamespace == mIdNamespace); + sender.emplace(mApi, &txn); + } + + if (NS_WARN_IF( + !ProcessWebRenderParentCommands(aDisplayList.mCommands, txn))) { + return false; + } + + if (aDisplayList.mDL && aValidTransaction && + !SetDisplayList(aDisplayList.mRect, std::move(aDisplayList.mDL.ref()), + aDisplayList.mDLDesc, aDisplayList.mResourceUpdates, + aDisplayList.mSmallShmems, aDisplayList.mLargeShmems, + aTxnStartTime, txn, aWrEpoch, aObserveLayersUpdate)) { + return false; + } + return true; +} + +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 nsCString& aTxnURL, + const TimeStamp& aFwdTime, nsTArray<CompositionPayload>&& aPayloads) { + if (mDestroyed) { + for (const auto& op : aToDestroy) { + DestroyActor(op); + } + return IPC_OK(); + } + + if (!IsRootWebRenderBridgeParent()) { + CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::URL, aTxnURL); + } + + AUTO_PROFILER_TRACING_MARKER("Paint", "SetDisplayList", GRAPHICS); + 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 observeLayersUpdate = ShouldParentObserveEpoch(); + + if (!ProcessDisplayListData(aDisplayList, wrEpoch, aTxnStartTime, + validTransaction, observeLayersUpdate)) { + wr::IpcResourceUpdateQueue::ReleaseShmems(this, aDisplayList.mSmallShmems); + wr::IpcResourceUpdateQueue::ReleaseShmems(this, aDisplayList.mLargeShmems); + return IPC_FAIL(this, "Failed to process DisplayListData."); + } + + if (!validTransaction && observeLayersUpdate) { + mCompositorBridge->ObserveLayersUpdate(GetLayersId(), + mChildLayersObserverEpoch, true); + } + + 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); + + return IPC_OK(); +} + +bool WebRenderBridgeParent::ProcessEmptyTransactionUpdates( + TransactionData& aData, bool* aScheduleComposite) { + *aScheduleComposite = false; + wr::TransactionBuilder txn; + 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(); + + if (aData.mIdNamespace == mIdNamespace && + !UpdateResources(aData.mResourceUpdates, aData.mSmallShmems, + aData.mLargeShmems, txn)) { + return false; + } + + if (!aData.mCommands.IsEmpty()) { + if (!ProcessWebRenderParentCommands(aData.mCommands, txn)) { + return false; + } + } + + if (ShouldParentObserveEpoch()) { + txn.Notify( + wr::Checkpoint::SceneBuilt, + MakeUnique<ScheduleObserveLayersUpdate>( + mCompositorBridge, GetLayersId(), mChildLayersObserverEpoch, 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 true; +} + +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 nsCString& aTxnURL, + const TimeStamp& aFwdTime, nsTArray<CompositionPayload>&& aPayloads) { + if (mDestroyed) { + for (const auto& op : aToDestroy) { + DestroyActor(op); + } + return IPC_OK(); + } + + 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; + + if (aTransactionData) { + bool scheduleComposite = false; + if (!ProcessEmptyTransactionUpdates(*aTransactionData, + &scheduleComposite)) { + wr::IpcResourceUpdateQueue::ReleaseShmems(this, + aTransactionData->mSmallShmems); + wr::IpcResourceUpdateQueue::ReleaseShmems(this, + aTransactionData->mLargeShmems); + return IPC_FAIL(this, "Failed to process empty transaction update."); + } + scheduleAnyComposite = scheduleAnyComposite || scheduleComposite; + } + + // 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(); + } 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); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult WebRenderBridgeParent::RecvSetFocusTarget( + const FocusTarget& aFocusTarget) { + UpdateAPZFocusState(aFocusTarget); + return IPC_OK(); +} + +mozilla::ipc::IPCResult WebRenderBridgeParent::RecvParentCommands( + nsTArray<WebRenderParentCommand>&& aCommands) { + if (mDestroyed) { + return IPC_OK(); + } + + wr::TransactionBuilder txn; + txn.SetLowPriority(!IsRootWebRenderBridgeParent()); + if (!ProcessWebRenderParentCommands(aCommands, txn)) { + return IPC_FAIL(this, "Invalid parent command found"); + } + + mApi->SendTransaction(txn); + 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; + wr::AutoTransactionSender sender(mApi, &txnForImageBridge); + + 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.isAsync(), + 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(); + mAsyncImageManager->UpdateAsyncImagePipeline( + op.pipelineId(), op.scBounds(), op.rotation(), op.filter(), + op.mixBlendMode()); + mAsyncImageManager->ApplyAsyncImageForPipeline(op.pipelineId(), aTxn, + txnForImageBridge); + break; + } + case WebRenderParentCommand::TOpUpdatedAsyncImagePipeline: { + const OpUpdatedAsyncImagePipeline& op = + cmd.get_OpUpdatedAsyncImagePipeline(); + aTxn.InvalidateRenderedFrame(); + mAsyncImageManager->ApplyAsyncImageForPipeline(op.pipelineId(), aTxn, + txnForImageBridge); + 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()) { + return false; + } + 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; + } + } + } + return true; +} + +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(); +} + +void WebRenderBridgeParent::FlushFrameGeneration() { + 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); + } +} + +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(); + + mDisablingNativeCompositor = true; +} + +void WebRenderBridgeParent::UpdateQualitySettings() { + wr::TransactionBuilder txn; + txn.UpdateQualitySettings(gfxVars::ForceSubpixelAAWherePossible()); + mApi->SendTransaction(txn); +} + +void WebRenderBridgeParent::UpdateDebugFlags() { + mApi->UpdateDebugFlags(gfxVars::WebRenderDebugFlags()); +} + +void WebRenderBridgeParent::UpdateProfilerUI() { + nsCString uiString = gfxVars::GetWebRenderProfilerUIOrDefault(); + mApi->SetProfilerUI(uiString); +} + +void WebRenderBridgeParent::UpdateMultithreading() { + mApi->EnableMultithreading(gfxVars::UseWebRenderMultithreading()); +} + +void WebRenderBridgeParent::UpdateBatchingParameters() { + uint32_t count = gfxVars::WebRenderBatchingLookback(); + mApi->SetBatchingLookback(count); +} + +#if defined(MOZ_WIDGET_ANDROID) +void WebRenderBridgeParent::RequestScreenPixels( + UiCompositorControllerParent* aController) { + mScreenPixelsTarget = aController; +} + +void WebRenderBridgeParent::MaybeCaptureScreenPixels() { + if (!mScreenPixelsTarget) { + return; + } + + if (mDestroyed) { + return; + } + MOZ_ASSERT(!mPaused); + + // This function should only get called in the root WRBP. + MOZ_ASSERT(IsRootWebRenderBridgeParent()); + + 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( + PTextureParent* aTexture, bool* aNeedsYFlip) { + *aNeedsYFlip = false; + if (mDestroyed || mPaused) { + return IPC_OK(); + } + + // 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(); + 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 bool& aAsync, wr::TransactionBuilder& aTxn, + wr::TransactionBuilder& aTxnForImageBridge) { + if (mDestroyed) { + return; + } + + MOZ_ASSERT(mAsyncCompositables.find(wr::AsUint64(aPipelineId)) == + mAsyncCompositables.end()); + + RefPtr<CompositableHost> host; + if (aAsync) { + RefPtr<ImageBridgeParent> imageBridge = + ImageBridgeParent::GetInstance(OtherPid()); + if (!imageBridge) { + return; + } + host = imageBridge->FindCompositable(aHandle); + } else { + host = FindCompositable(aHandle); + } + 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); +} + +mozilla::ipc::IPCResult WebRenderBridgeParent::RecvSetLayersObserverEpoch( + const LayersObserverEpoch& aChildEpoch) { + if (mDestroyed) { + return IPC_OK(); + } + mChildLayersObserverEpoch = aChildEpoch; + return IPC_OK(); +} + +mozilla::ipc::IPCResult WebRenderBridgeParent::RecvClearCachedResources() { + if (mDestroyed) { + return IPC_OK(); + } + + // Clear resources + wr::TransactionBuilder txn; + txn.SetLowPriority(true); + txn.ClearDisplayList(GetNextWrEpoch(), mPipelineId); + txn.Notify( + wr::Checkpoint::SceneBuilt, + MakeUnique<ScheduleObserveLayersUpdate>( + mCompositorBridge, GetLayersId(), mChildLayersObserverEpoch, false)); + mApi->SendTransaction(txn); + + // Schedule generate frame to clean up Pipeline + ScheduleGenerateFrame(); + + 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); + + return GetNextWrEpoch(); // Update webrender epoch +} + +mozilla::ipc::IPCResult WebRenderBridgeParent::RecvInvalidateRenderedFrame() { + // This function should only get called in the root WRBP + MOZ_ASSERT(IsRootWebRenderBridgeParent()); + + InvalidateRenderedFrame(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult WebRenderBridgeParent::RecvScheduleComposite() { + // Caller of LayerManager::ScheduleComposite() expects that it trigger + // composite. Then we do not want to skip generate frame. + ScheduleForcedGenerateFrame(); + return IPC_OK(); +} + +void WebRenderBridgeParent::InvalidateRenderedFrame() { + if (mDestroyed) { + return; + } + + wr::TransactionBuilder fastTxn(/* aUseSceneBuilderThread */ false); + fastTxn.InvalidateRenderedFrame(); + mApi->SendTransaction(fastTxn); +} + +void WebRenderBridgeParent::ScheduleForcedGenerateFrame() { + if (mDestroyed) { + return; + } + + InvalidateRenderedFrame(); + ScheduleGenerateFrame(); +} + +mozilla::ipc::IPCResult WebRenderBridgeParent::RecvCapture() { + if (!mDestroyed) { + mApi->Capture(); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult WebRenderBridgeParent::RecvToggleCaptureSequence() { + if (!mDestroyed) { + mApi->ToggleCaptureSequence(); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult WebRenderBridgeParent::RecvSyncWithCompositor() { + FlushSceneBuilds(); + if (RefPtr<WebRenderBridgeParent> root = GetRootWebRenderBridgeParent()) { + root->FlushFrameGeneration(); + } + 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 (!mCompositorBridge->SetTestSampleTime(GetLayersId(), aTime)) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult WebRenderBridgeParent::RecvLeaveTestMode() { + if (mDestroyed) { + return IPC_FAIL_NO_REASON(this); + } + + mCompositorBridge->LeaveTestMode(GetLayersId()); + 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) { + mCompositorBridge->GetAPZTestData(GetLayersId(), aOutData); + return IPC_OK(); +} + +mozilla::ipc::IPCResult WebRenderBridgeParent::RecvGetFrameUniformity( + FrameUniformityData* aOutData) { + 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::CompositeIfNeeded() { + if (mSkippedComposite) { + mSkippedComposite = false; + if (mCompositorScheduler) { + mCompositorScheduler->ScheduleComposition(); + } + } +} + +void WebRenderBridgeParent::CompositeToTarget(VsyncId aId, + 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); + + AUTO_PROFILER_TRACING_MARKER("Paint", "CompositeToTarget", GRAPHICS); + if (mPaused || !mReceivedDisplayList) { + ResetPreviousSampleTime(); + mCompositionOpportunityId = mCompositionOpportunityId.Next(); + PROFILER_MARKER_TEXT("SkippedComposite", GRAPHICS, {}, + mPaused ? "Paused"_ns : "No display list"_ns); + return; + } + + if (mSkippedComposite || + wr::RenderThread::Get()->TooManyPendingFrames(mApi->GetId())) { + // Render thread is busy, try next time. + mSkippedComposite = true; + 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, {}, + "Too many pending frames"); + return; + } + + mCompositionOpportunityId = mCompositionOpportunityId.Next(); + MaybeGenerateFrame(aId, /* aForceGenerateFrame */ false); +} + +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) { + // This function should only get called in the root WRBP + MOZ_ASSERT(IsRootWebRenderBridgeParent()); + + if (CompositorBridgeParent* cbp = GetRootCompositorBridgeParent()) { + // Skip WR render during paused state. + if (cbp->IsPaused()) { + TimeStamp now = TimeStamp::NowUnfuzzed(); + PROFILER_MARKER_TEXT("SkippedComposite", GRAPHICS, + 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(false /* useSceneBuilderThread */); + // Handle transaction that is related to DisplayList. + wr::TransactionBuilder sceneBuilderTxn; + 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(); + } + + bool generateFrame = mAsyncImageManager->GetAndResetWillGenerateFrame() || + !fastTxn.IsEmpty() || aForceGenerateFrame; + + 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(); + } + } + + SetOMTASampleTime(); + SetAPZSampleTime(); + + wr::RenderThread::Get()->IncPendingFrameCount(mApi->GetId(), aId, start); + +#if defined(ENABLE_FRAME_LATENCY_LOG) + auto startTime = TimeStamp::Now(); + mApi->SetFrameStartTime(startTime); +#endif + + MOZ_ASSERT(generateFrame); + fastTxn.GenerateFrame(aId); + 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(); + } +} + +void WebRenderBridgeParent::HoldPendingTransactionId( + const wr::Epoch& aWrEpoch, TransactionId aTransactionId, + bool aContainsSVGGroup, const VsyncId& aVsyncId, + const TimeStamp& aVsyncStartTime, const TimeStamp& aRefreshStartTime, + const TimeStamp& aTxnStartTime, const nsCString& 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(); + 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(); + return; + } + } + } + + CompositeToTarget(mCompositorScheduler->GetLastVsyncId(), nullptr, nullptr); +} + +TransactionId 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) { + TransactionId id{0}; + 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); + if (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); + + id = transactionId.mId; + mPendingTransactionIds.pop_front(); + } + return id; +} + +LayersId WebRenderBridgeParent::GetLayersId() const { + return wr::AsLayersId(mPipelineId); +} + +void WebRenderBridgeParent::ScheduleGenerateFrame() { + if (mCompositorScheduler) { + mAsyncImageManager->SetWillGenerateFrame(); + mCompositorScheduler->ScheduleComposition(); + } +} + +void WebRenderBridgeParent::FlushRendering(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(); + if (aWaitForPresent) { + FlushFramePresentation(); + } +} + +void WebRenderBridgeParent::SetClearColor(const gfx::DeviceColor& aColor) { + MOZ_ASSERT(IsRootWebRenderBridgeParent()); + + if (!IsRootWebRenderBridgeParent() || mDestroyed) { + return; + } + + mApi->SetClearColor(aColor); +} + +void WebRenderBridgeParent::Pause() { + MOZ_ASSERT(IsRootWebRenderBridgeParent()); + + if (!IsRootWebRenderBridgeParent() || mDestroyed) { + return; + } + + mApi->Pause(); + mPaused = true; +} + +bool WebRenderBridgeParent::Resume() { + MOZ_ASSERT(IsRootWebRenderBridgeParent()); + + if (!IsRootWebRenderBridgeParent() || mDestroyed) { + return false; + } + + if (!mApi->Resume()) { + return false; + } + + // Ensure we generate and render a frame immediately. + ScheduleForcedGenerateFrame(); + + mPaused = false; + return true; +} + +void WebRenderBridgeParent::ClearResources() { + if (!mApi) { + return; + } + + wr::Epoch wrEpoch = GetNextWrEpoch(); + mReceivedDisplayList = false; + // Schedule generate frame to clean up Pipeline + ScheduleGenerateFrame(); + + // 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; + 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(); + } + + 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 +} + +bool WebRenderBridgeParent::ShouldParentObserveEpoch() { + if (mParentLayersObserverEpoch == mChildLayersObserverEpoch) { + return false; + } + + mParentLayersObserverEpoch = mChildLayersObserverEpoch; + return true; +} + +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(); +} + +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, /* aUseWebRender */ true)) { + 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); + + TextureFactoryIdentifier ident( + mApi->GetBackendType(), mApi->GetCompositorType(), XRE_GetProcessType(), + mApi->GetMaxTextureSize(), false, mApi->GetUseANGLE(), + mApi->GetUseDComp(), mAsyncImageManager->UseCompositorWnd(), false, false, + false, 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); +} + +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 layers +} // namespace mozilla diff --git a/gfx/layers/wr/WebRenderBridgeParent.h b/gfx/layers/wr/WebRenderBridgeParent.h new file mode 100644 index 0000000000..3c91b8c84e --- /dev/null +++ b/gfx/layers/wr/WebRenderBridgeParent.h @@ -0,0 +1,535 @@ +/* -*- 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/LayerManager.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" + +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 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 layers::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 UpdateMultithreading(); + void UpdateBatchingParameters(); + 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 nsCString& 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 nsCString& aTxnURL, + const TimeStamp& aFwdTime, + nsTArray<CompositionPayload>&& aPayloads) override; + mozilla::ipc::IPCResult RecvSetFocusTarget( + const FocusTarget& aFocusTarget) override; + mozilla::ipc::IPCResult RecvParentCommands( + nsTArray<WebRenderParentCommand>&& commands) override; + mozilla::ipc::IPCResult RecvGetSnapshot(PTextureParent* aTexture, + bool* aNeedsYFlip) override; + + mozilla::ipc::IPCResult RecvSetLayersObserverEpoch( + const LayersObserverEpoch& aChildEpoch) override; + + mozilla::ipc::IPCResult RecvClearCachedResources() override; + mozilla::ipc::IPCResult RecvInvalidateRenderedFrame() override; + mozilla::ipc::IPCResult RecvScheduleComposite() override; + mozilla::ipc::IPCResult RecvCapture() override; + mozilla::ipc::IPCResult RecvToggleCaptureSequence() 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; + + 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, gfx::DrawTarget* aTarget, + const gfx::IntRect* aRect = nullptr) override; + TimeDuration GetVsyncInterval() const override; + + // CompositableParentManager + bool IsSameProcess() const override; + base::ProcessId GetChildProcessId() 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 nsCString& aTxnURL, + const TimeStamp& aFwdTime, const bool aIsFirstPaint, + nsTArray<CompositionPayload>&& aPayloads, + const bool aUseForTelemetry = true); + TransactionId LastPendingTransactionId(); + TransactionId FlushTransactionIdsForEpoch( + const wr::Epoch& aEpoch, const VsyncId& aCompositeStartId, + const TimeStamp& aCompositeStartTime, const TimeStamp& aRenderStartTime, + const TimeStamp& aEndTime, UiCompositorControllerParent* aUiController, + wr::RendererStats* aStats = nullptr, + nsTArray<FrameStats>* aOutputStats = nullptr); + void NotifySceneBuiltForEpoch(const wr::Epoch& aEpoch, + const TimeStamp& aEndTime); + + void CompositeIfNeeded(); + + 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(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(); + + /** + * 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(); + + /** + * 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(); + + 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); + + /** + * Write the frames collected since the call to BeginRecording to disk. + * + * If there is not currently a recorder, this is a no-op. + */ + RefPtr<wr::WebRenderAPI::WriteCollectedFramesPromise> WriteCollectedFrames(); + +#if defined(MOZ_WIDGET_ANDROID) + /** + * Request a screengrab for android + */ + void RequestScreenPixels(UiCompositorControllerParent* aController); + void MaybeCaptureScreenPixels(); +#endif + /** + * Return the frames collected since the call to BeginRecording encoded + * as data URIs. + * + * If there is not currently a recorder, this is a no-op and the promise will + * be rejected. + */ + RefPtr<wr::WebRenderAPI::GetCollectedFramesPromise> GetCollectedFrames(); + + void DisableNativeCompositor(); + void AddPendingScrollPayload(CompositionPayload& aPayload, + const VsyncId& aCompositeStartId); + + nsTArray<CompositionPayload> TakePendingScrollPayload( + const VsyncId& aCompositeStartId); + + RefPtr<WebRenderBridgeParentRef> GetWebRenderBridgeParentRef(); + + 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 aObserveLayersUpdate); + + bool SetDisplayList(const LayoutDeviceRect& aRect, ipc::ByteBuf&& aDL, + 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 aObserveLayersUpdate); + + 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 AddPrivateExternalImage(wr::ExternalImageId aExtId, wr::ImageKey aKey, + wr::ImageDescriptor aDesc, + wr::TransactionBuilder& aResources); + bool UpdatePrivateExternalImage(wr::ExternalImageId aExtId, wr::ImageKey aKey, + const wr::ImageDescriptor& aDesc, + const ImageIntRect& aDirtyRect, + wr::TransactionBuilder& aResources); + 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); + + 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 bool& aAsync, + 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(); + bool ShouldParentObserveEpoch(); + mozilla::ipc::IPCResult HandleShutdown(); + + 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(); + void FlushFramePresentation(); + + void MaybeGenerateFrame(VsyncId aId, bool aForceGenerateFrame); + + 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 nsCString& 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; + // These fields keep track of the latest layer observer epoch values in the + // child and the parent. mChildLayersObserverEpoch is the latest epoch value + // received from the child. mParentLayersObserverEpoch is the latest epoch + // value that we have told BrowserParent about (via ObserveLayerUpdate). + LayersObserverEpoch mChildLayersObserverEpoch; + LayersObserverEpoch mParentLayersObserverEpoch; + + 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 + bool mPaused; + bool mDestroyed; + bool mReceivedDisplayList; + bool mIsFirstPaint; + bool mSkippedComposite; + bool mDisablingNativeCompositor; + // These payloads are being used for SCROLL_PRESENT_LATENCY telemetry + DataMutex<nsClassHashtable<nsUint64HashKey, nsTArray<CompositionPayload>>> + mPendingScrollPayloads; +}; + +// 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..2576cbd8ec --- /dev/null +++ b/gfx/layers/wr/WebRenderCanvasRenderer.cpp @@ -0,0 +1,81 @@ +/* -*- 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 "SharedSurfaceGL.h" +#include "WebRenderBridgeChild.h" +#include "RenderRootStateManager.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(); + } + + if (!mPipelineId) { + // Alloc async image pipeline id. + mPipelineId = Some( + mManager->WrBridge()->GetCompositorBridgeChild()->GetNextPipelineId()); + mManager->AddPipelineIdForCompositable(mPipelineId.ref(), + mCanvasClient->GetIPCHandle()); + } + + return true; +} + +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..0aa6d5d580 --- /dev/null +++ b/gfx/layers/wr/WebRenderCanvasRenderer.h @@ -0,0 +1,54 @@ +/* -*- 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 "ShareableCanvasRenderer.h" + +namespace mozilla { +namespace layers { + +class RenderRootStateManager; + +class WebRenderCanvasRenderer : public ShareableCanvasRenderer { + public: + explicit WebRenderCanvasRenderer(RenderRootStateManager* aManager) + : mManager(aManager) {} + + CompositableForwarder* GetForwarder() override; + + protected: + 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 ClearCachedResources() override; + + void UpdateCompositableClientForEmptyTransaction(); + + Maybe<wr::PipelineId> GetPipelineId() { return mPipelineId; } + + protected: + Maybe<wr::PipelineId> mPipelineId; +}; + +} // 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..c36349e7b6 --- /dev/null +++ b/gfx/layers/wr/WebRenderCommandBuilder.cpp @@ -0,0 +1,2689 @@ +/* -*- 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 "BasicLayers.h" +#include "Layers.h" +#include "mozilla/AutoRestore.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/EffectCompositor.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/SVGGeometryFrame.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Logging.h" +#include "mozilla/gfx/Types.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 "WebRenderCanvasRenderer.h" +#include "LayerTreeInvalidation.h" + +namespace mozilla { +namespace layers { + +using namespace gfx; +static bool PaintByLayer(nsDisplayItem* aItem, + nsDisplayListBuilder* aDisplayListBuilder, + const RefPtr<BasicLayerManager>& aManager, + gfxContext* aContext, const gfx::Size& aScale, + const std::function<void()>& aPaintFunc); +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); +} + +// 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 + + IntRect 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 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; + + // properties that are used to emulate layer tree invalidation + Matrix mMatrix; // updated to track the current transform to device space + RefPtr<BasicLayerManager> mLayerManager; + + // 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. + std::vector<RefPtr<SourceSurface>> mExternalSurfaces; + + IntRect mImageRect; + + BlobItemData(DIGroup* aGroup, nsDisplayItem* aItem) + : 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, + std::vector<RefPtr<SourceSurface>>& aExternalSurfaces, + RenderRootStateManager* aManager, wr::IpcResourceUpdateQueue& aResources) { + aRecorder->TakeExternalSurfaces(aExternalSurfaces); + + for (auto& surface : 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(surface, 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; + Matrix mTransform; + + // Paint the list of aChildren display items. + void PaintContainerItem(DIGroup* aGroup, nsDisplayItem* aItem, + BlobItemData* aData, const IntRect& aItemBounds, + 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, const StackingContextHelper& aSc); + // Builds a group of display items without promoting anything to active. + void ConstructGroupInsideInactive(WebRenderCommandBuilder* aCommandBuilder, + wr::DisplayListBuilder& aBuilder, + wr::IpcResourceUpdateQueue& aResources, + DIGroup* aGroup, nsDisplayList* aList, + const StackingContextHelper& aSc); + // Helper method for processing a single inactive item + void ConstructItemInsideInactive(WebRenderCommandBuilder* aCommandBuilder, + wr::DisplayListBuilder& aBuilder, + wr::IpcResourceUpdateQueue& aResources, + DIGroup* aGroup, nsDisplayItem* aItem, + const StackingContextHelper& aSc); + ~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 go through BasicLayerManager composition which clips to + // the BuildingRect + aGeometry.mBounds = aGeometry.mBounds.Intersect(aItem->GetBuildingRect()); + } + + return !aGeometry.mBounds.IsEqualEdges(aData->mGeometry->mBounds); +} + +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 + nsTHashtable<nsPtrHashKey<BlobItemData>> mDisplayItems; + + IntRect mInvalidRect; + nsRect mGroupBounds; + 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 + IntRect mPreservedRect; + IntRect mActualBounds; + int32_t mAppUnitsPerDevPixel; + gfx::Size 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. + IntRect 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 IntRect& aRect) { + auto r = aRect.Intersect(mPreservedRect); + // Empty rects get dropped + if (!r.IsEmpty()) { + mInvalidRect = mInvalidRect.Union(r); + } + } + + IntRect ItemBounds(nsDisplayItem* aItem) { + BlobItemData* data = GetBlobItemData(aItem); + return data->mRect; + } + + void ClearItems() { + GP("items: %d\n", mDisplayItems.Count()); + for (auto iter = mDisplayItems.Iter(); !iter.Done(); iter.Next()) { + BlobItemData* data = iter.Get()->GetKey(); + GP("Deleting %p-%d\n", data->mFrame, data->mDisplayItemKey); + iter.Remove(); + delete data; + } + } + + void ClearImageKey(RenderRootStateManager* aManager, bool aForce = false) { + if (mKey) { + MOZ_RELEASE_ASSERT(aForce || mInvalidRect.IsEmpty()); + aManager->AddBlobImageKeyForDiscard(*mKey); + mKey = Nothing(); + } + mFonts.clear(); + } + + static IntRect 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 IntRect(); + } + return RoundedOut(aMatrix.TransformBounds( + ToRect(nsLayoutUtils::RectToGfxRect(aBounds, aAppUnitsPerDevPixel)))); + } + + void 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; + 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); + + IntRect 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, mVisibleRect.TopLeft().y, + 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; + } else if (aData->mInvalid || + /* XXX: handle image load invalidation */ ( + 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 + IntRect 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; + } 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()); + IntRect transformedRect = + ToDeviceSpace(clippedBounds, aMatrix, appUnitsPerDevPixel); + aData->mRect = transformedRect.Intersect(mClippedImageBounds); + InvalidateRect(aData->mRect); + + aData->mInvalid = 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()); + IntRect transformedRect = + ToDeviceSpace(clippedBounds, aMatrix, appUnitsPerDevPixel); + InvalidateRect(aData->mRect); + aData->mRect = transformedRect.Intersect(mClippedImageBounds); + InvalidateRect(aData->mRect); + + GP("ClipChange: %s %d %d %d %d\n", aItem->Name(), aData->mRect.x, + aData->mRect.y, aData->mRect.XMost(), aData->mRect.YMost()); + + } else if (!aMatrix.ExactlyEquals(aData->mMatrix)) { + // We haven't detected any changes so far. Unfortunately we don't + // currently have a good way of checking if the transform has changed + // so we just store it and see if it see if it has changed. + // If we want this to go faster, we can probably put a flag on the + // frame using the style sytem UpdateTransformLayer hint and check for + // that. + + UniquePtr<nsDisplayItemGeometry> geometry( + aItem->AllocateGeometry(aBuilder)); + if (!IsContainerLayerItem(aItem)) { + // the bounds of layer items can change on us + // other items shouldn't + MOZ_RELEASE_ASSERT( + geometry->mBounds.IsEqualEdges(aData->mGeometry->mBounds)); + } else { + aData->mGeometry = std::move(geometry); + } + nsRect clippedBounds = clip.ApplyNonRoundedIntersection( + aData->mGeometry->ComputeInvalidationRegion()); + IntRect transformedRect = + ToDeviceSpace(clippedBounds, aMatrix, appUnitsPerDevPixel); + InvalidateRect(aData->mRect); + aData->mRect = transformedRect.Intersect(mClippedImageBounds); + InvalidateRect(aData->mRect); + + GP("TransformChange: %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); + IntRect transformedRect = + ToDeviceSpace(clippedBounds, aMatrix, appUnitsPerDevPixel); + InvalidateRect(aData->mRect); + aData->mRect = transformedRect.Intersect(mClippedImageBounds); + InvalidateRect(aData->mRect); + GP("DetectContainerLayerPropertiesBoundsChange change\n"); + } else { + // Handle changes in mClippedImageBounds + nsRect clippedBounds = clip.ApplyNonRoundedIntersection( + geometry->ComputeInvalidationRegion()); + IntRect 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); + } 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()); + IntRect 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); + } else { + GP("NoChange: %s %d %d %d %d\n", aItem->Name(), aData->mRect.x, + aData->mRect.y, aData->mRect.XMost(), aData->mRect.YMost()); + } + } + } + } + mActualBounds.OrWith(aData->mRect); + aData->mClip = clip; + aData->mMatrix = aMatrix; + aData->mImageRect = mClippedImageBounds; + GP("post mInvalidRect: %d %d %d %d\n", mInvalidRect.x, mInvalidRect.y, + mInvalidRect.width, mInvalidRect.height); + } + + void EndGroup(WebRenderLayerManager* aWrManager, + nsDisplayListBuilder* aDisplayListBuilder, + wr::DisplayListBuilder& aBuilder, + wr::IpcResourceUpdateQueue& aResources, Grouper* aGrouper, + nsDisplayItem* aStartItem, nsDisplayItem* aEndItem) { + GP("\n\n"); + GP("Begin EndGroup\n"); + + mVisibleRect = mVisibleRect.Intersect(ViewAs<LayerPixel>( + mActualBounds, PixelCastJustification::LayerIsImage)); + + if (mVisibleRect.IsEmpty()) { + return; + } + + // Invalidate any unused items + GP("mDisplayItems\n"); + for (auto iter = mDisplayItems.Iter(); !iter.Done(); iter.Next()) { + BlobItemData* data = iter.Get()->GetKey(); + GP(" : %p-%d\n", data->mFrame, data->mDisplayItemKey); + if (!data->mUsed) { + GP("Invalidate unused: %p-%d\n", data->mFrame, data->mDisplayItemKey); + InvalidateRect(data->mRect); + iter.Remove(); + delete data; + } else { + data->mUsed = 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 + LayoutDeviceToLayerScale2D scale(mScale.width, mScale.height); + 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; + } + + gfx::SurfaceFormat format = gfx::SurfaceFormat::B8G8R8A8; + 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 = gfx::Factory::CreateDrawTarget( + gfx::BackendType::SKIA, gfx::IntSize(1, 1), format); + + RefPtr<gfx::DrawTarget> dt = gfx::Factory::CreateRecordingDrawTarget( + recorder, dummyDt, mLayerBounds.ToUnknownRect()); + // Setup the gfxContext + RefPtr<gfxContext> context = gfxContext::CreateOrNull(dt); + context->SetMatrix( + Matrix::Scaling(mScale.width, mScale.height) + .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; + } + + // Reset mHitInfo, it will get updated inside PaintItemRange + mHitInfo = CompositorHitTestInvisibleToHit; + + 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()) { + 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 { + 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); + + auto bottomRight = dirtyRect.BottomRight(); + GP("check invalid %d %d - %d %d\n", bottomRight.x, bottomRight.y, + 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.origin.x, dest.origin.y, + dest.size.width, dest.size.height); + gfx::SamplingFilter sampleFilter = gfx::SamplingFilter:: + LINEAR; // nsLayoutUtils::GetSamplingFilterForFrame(aItem->Frame()); + bool backfaceHidden = false; + + // 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; + } + + // XXX - clipping the item against the paint rect breaks some content. + // cf. Bug 1455422. + // wr::LayoutRect clip = wr::ToLayoutRect(bounds.Intersect(mVisibleRect)); + + aBuilder.PushHitTest(dest, dest, !backfaceHidden, mScrollId, hitInfo, + SideBits::eNone); + + aBuilder.PushImage(dest, dest, !backfaceHidden, + wr::ToImageRendering(sampleFilter), + wr::AsImageKey(*mKey)); + } + + void PaintItemRange(Grouper* aGrouper, nsDisplayItem* aStartItem, + nsDisplayItem* aEndItem, gfxContext* aContext, + WebRenderDrawEventRecorder* aRecorder, + RenderRootStateManager* aRootManager, + wr::IpcResourceUpdateQueue& aResources) { + LayerIntSize size = mVisibleRect.Size(); + for (nsDisplayItem* item = aStartItem; item != aEndItem; + item = item->GetAbove()) { + BlobItemData* data = GetBlobItemData(item); + IntRect bounds = data->mRect; + auto bottomRight = bounds.BottomRight(); + + GP("Trying %s %p-%d %d %d %d %d\n", item->Name(), item->Frame(), + item->GetPerFrameKey(), bounds.x, bounds.y, bounds.XMost(), + bounds.YMost()); + + if (item->GetType() == DisplayItemType::TYPE_COMPOSITOR_HITTEST_INFO) { + // 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. + mHitInfo += + static_cast<nsDisplayCompositorHitTestInfo*>(item)->HitTestFlags(); + continue; + } + + GP("paint check invalid %d %d - %d %d\n", bottomRight.x, bottomRight.y, + size.width, size.height); + // skip empty items + if (bounds.IsEmpty()) { + continue; + } + + bool dirty = true; + auto preservedBounds = bounds.Intersect(mPreservedRect); + if (!mInvalidRect.Contains(preservedBounds)) { + GP("Passing\n"); + dirty = false; + BlobItemData* data = GetBlobItemData(item); + 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) { + GP("doing children in EndGroup\n"); + aGrouper->PaintContainerItem(this, item, data, bounds, 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); + } + } + + ~DIGroup() { + GP("Group destruct\n"); + for (auto iter = mDisplayItems.Iter(); !iter.Done(); iter.Next()) { + BlobItemData* data = iter.Get()->GetKey(); + GP("Deleting %p-%d\n", data->mFrame, data->mDisplayItemKey); + iter.Remove(); + 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_RELEASE_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.PutEntry(data); + } + data->mUsed = true; + return data; +} + +void Grouper::PaintContainerItem(DIGroup* aGroup, nsDisplayItem* aItem, + BlobItemData* aData, + const IntRect& aItemBounds, + 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)) { + // We don't currently support doing invalidation inside 3d transforms. + // For now just paint it as a single item. + BlobItemData* data = GetBlobItemDataForGroup(aItem, aGroup); + if (data->mLayerManager->GetRoot()) { + data->mLayerManager->BeginTransaction(); + data->mLayerManager->EndTransaction( + FrameLayerBuilder::DrawPaintedLayer, mDisplayListBuilder); + TakeExternalSurfaces(aRecorder, data->mExternalSurfaces, aRootManager, + aResources); + aContext->GetDrawTarget()->FlushItem(aItemBounds); + } + } else { + aContext->Multiply(ThebesMatrix(trans2d)); + aGroup->PaintItemRange(this, aChildren->GetBottom(), nullptr, 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->GetBottom(), nullptr, 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->GetBottom(), nullptr, 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->GetBottom(), nullptr, 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); + maskItem->SetPaintRect(maskItem->GetClippedBounds(mDisplayListBuilder)); + 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->GetBottom(), nullptr, + 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"); + // We don't currently support doing invalidation inside nsDisplayFilters + // for now just paint it as a single item + BlobItemData* data = GetBlobItemDataForGroup(aItem, aGroup); + if (data->mLayerManager->GetRoot()) { + data->mLayerManager->BeginTransaction(); + static_cast<nsDisplayFilters*>(aItem)->PaintAsLayer( + mDisplayListBuilder, aContext, data->mLayerManager); + if (data->mLayerManager->InTransaction()) { + data->mLayerManager->AbortTransaction(); + } + TakeExternalSurfaces(aRecorder, data->mExternalSurfaces, aRootManager, + aResources); + aContext->GetDrawTarget()->FlushItem(aItemBounds); + } + break; + } + + default: + aGroup->PaintItemRange(this, aChildren->GetBottom(), nullptr, 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; +}; + +static bool IsItemProbablyActive( + nsDisplayItem* aItem, mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const mozilla::layers::StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder, bool aSiblingActive); + +static bool HasActiveChildren(const nsDisplayList& aList, + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const mozilla::layers::StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + for (nsDisplayItem* i = aList.GetBottom(); i; i = i->GetAbove()) { + if (IsItemProbablyActive(i, aBuilder, aResources, aSc, aManager, + aDisplayListBuilder, false)) { + return true; + } + } + return false; +} + +// 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 bool IsItemProbablyActive( + nsDisplayItem* aItem, mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const mozilla::layers::StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder, + bool aHasActivePrecedingSibling) { + 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); + GP("active: %d\n", transformItem->MayBeAnimated(aDisplayListBuilder)); + return transformItem->MayBeAnimated(aDisplayListBuilder, false) || + !is2D || + HasActiveChildren(*transformItem->GetChildren(), aBuilder, + aResources, aSc, aManager, aDisplayListBuilder); + } + case DisplayItemType::TYPE_OPACITY: { + nsDisplayOpacity* opacityItem = static_cast<nsDisplayOpacity*>(aItem); + bool active = opacityItem->NeedsActiveLayer(aDisplayListBuilder, + opacityItem->Frame(), false); + GP("active: %d\n", active); + return active || + HasActiveChildren(*opacityItem->GetChildren(), aBuilder, + aResources, aSc, aManager, aDisplayListBuilder); + } + case DisplayItemType::TYPE_FOREIGN_OBJECT: { + return true; + } + case DisplayItemType::TYPE_SVG_GEOMETRY: { + if (StaticPrefs::gfx_webrender_svg_images()) { + auto* svgItem = static_cast<DisplaySVGGeometry*>(aItem); + return svgItem->ShouldBeActive(aBuilder, aResources, aSc, aManager, + aDisplayListBuilder); + } + return false; + } + 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. */ + return aHasActivePrecedingSibling || + HasActiveChildren(*aItem->GetChildren(), aBuilder, aResources, aSc, + aManager, aDisplayListBuilder); + } + case DisplayItemType::TYPE_WRAP_LIST: + case DisplayItemType::TYPE_CONTAINER: + case DisplayItemType::TYPE_MASK: + case DisplayItemType::TYPE_PERSPECTIVE: { + if (aItem->GetChildren()) { + return HasActiveChildren(*aItem->GetChildren(), aBuilder, aResources, + aSc, aManager, aDisplayListBuilder); + } + return false; + } + case DisplayItemType::TYPE_FILTER: { + nsDisplayFilters* filters = static_cast<nsDisplayFilters*>(aItem); + return filters->CanCreateWebRenderCommands(); + } + default: + // TODO: handle other items? + return false; + } +} + +// 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, + const StackingContextHelper& aSc) { + DIGroup* currentGroup = aGroup; + + nsDisplayItem* item = aList->GetBottom(); + nsDisplayItem* startOfCurrentGroup = item; + RenderRootStateManager* manager = + aCommandBuilder->mManager->GetRenderRootStateManager(); + // We need to track whether we have active siblings for mixed blend mode. + bool encounteredActiveItem = false; + while (item) { + if (IsItemProbablyActive(item, aBuilder, aResources, aSc, manager, + mDisplayListBuilder, encounteredActiveItem)) { + 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.mGroupBounds = currentGroup->mGroupBounds; + 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) + .ToUnknownRect(); + groupData->mFollowingGroup.mActualBounds = IntRect(); + + currentGroup->EndGroup(aCommandBuilder->mManager, aDisplayListBuilder, + aBuilder, aResources, this, startOfCurrentGroup, + item); + + { + auto spaceAndClipChain = mClipManager.SwitchItem(item); + wr::SpaceAndClipChainHelper saccHelper(aBuilder, spaceAndClipChain); + + sIndent++; + // Note: this call to CreateWebRenderCommands can recurse back into + // this function. + bool createdWRCommands = item->CreateWebRenderCommands( + aBuilder, aResources, aSc, manager, mDisplayListBuilder); + sIndent--; + MOZ_RELEASE_ASSERT( + createdWRCommands, + "active transforms should always succeed at creating " + "WebRender commands"); + } + + currentGroup = &groupData->mFollowingGroup; + + startOfCurrentGroup = item->GetAbove(); + } else { // inactive item + ConstructItemInsideInactive(aCommandBuilder, aBuilder, aResources, + currentGroup, item, aSc); + } + + item = item->GetAbove(); + } + + currentGroup->EndGroup(aCommandBuilder->mManager, aDisplayListBuilder, + aBuilder, aResources, this, startOfCurrentGroup, + nullptr); +} + +// This does a pass over the display lists and will join the display items +// into a single group. +void Grouper::ConstructGroupInsideInactive( + WebRenderCommandBuilder* aCommandBuilder, wr::DisplayListBuilder& aBuilder, + wr::IpcResourceUpdateQueue& aResources, DIGroup* aGroup, + nsDisplayList* aList, const StackingContextHelper& aSc) { + nsDisplayItem* item = aList->GetBottom(); + while (item) { + ConstructItemInsideInactive(aCommandBuilder, aBuilder, aResources, aGroup, + item, aSc); + item = item->GetAbove(); + } +} + +bool BuildLayer(nsDisplayItem* aItem, BlobItemData* aData, + nsDisplayListBuilder* aDisplayListBuilder, + const gfx::Size& aScale); + +void Grouper::ConstructItemInsideInactive( + WebRenderCommandBuilder* aCommandBuilder, wr::DisplayListBuilder& aBuilder, + wr::IpcResourceUpdateQueue& aResources, DIGroup* aGroup, + nsDisplayItem* aItem, const StackingContextHelper& aSc) { + 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; + + // we compute the geometry change here because we have the transform around + // still + 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. + IntRect oldClippedImageBounds = aGroup->mClippedImageBounds; + aGroup->mClippedImageBounds = + aGroup->mClippedImageBounds.Intersect(data->mRect); + + if (aItem->GetType() == DisplayItemType::TYPE_FILTER) { + gfx::Size scale(1, 1); + // If ComputeDifferences 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. + if (BuildLayer(aItem, data, mDisplayListBuilder, scale)) { + data->mInvalid = true; + aGroup->InvalidateRect(data->mRect); + } + } else if (aItem->GetType() == DisplayItemType::TYPE_TRANSFORM) { + nsDisplayTransform* transformItem = static_cast<nsDisplayTransform*>(aItem); + const Matrix4x4Flagged& t = transformItem->GetTransform(); + Matrix t2d; + bool is2D = t.Is2D(&t2d); + if (!is2D) { + // We'll use BasicLayerManager to handle 3d transforms. + gfx::Size scale(1, 1); + // If ComputeDifferences 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. + if (BuildLayer(aItem, data, mDisplayListBuilder, scale)) { + data->mInvalid = true; + aGroup->InvalidateRect(data->mRect); + } + } else { + Matrix m = mTransform; + + GP("t2d: %f %f\n", t2d._31, t2d._32); + mTransform.PreMultiply(t2d); + GP("mTransform: %f %f\n", mTransform._31, mTransform._32); + ConstructGroupInsideInactive(aCommandBuilder, aBuilder, aResources, + aGroup, children, aSc); + + mTransform = m; + } + } else if (children) { + sIndent++; + ConstructGroupInsideInactive(aCommandBuilder, aBuilder, aResources, aGroup, + children, aSc); + sIndent--; + } + + GP("Including %s of %d\n", aItem->Name(), aGroup->mDisplayItems.Count()); + aGroup->mClippedImageBounds = oldClippedImageBounds; +} + +/* 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); + 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; + + gfx::Size scale = aSc.GetInheritedScale(); + GP("Inherrited scale %f %f\n", scale.width, scale.height); + + auto trans = + ViewAs<LayerPixel>(aSc.GetSnappingSurfaceTransform().GetTranslation()); + auto snappedTrans = LayerIntPoint::Floor(trans); + LayerPoint residualOffset = trans - snappedTrans; + + auto p = group.mGroupBounds; + auto q = groupBounds; + // XXX: we currently compute the paintRect for the entire svg, but if the svg + // gets split into multiple groups (blobs), then they will all inherit this + // overall size even though they may each be much smaller. This can lead to + // allocating much larger textures than necessary in webrender. + // + // Don't bother fixing this unless we run into this in the real world, though. + auto layerBounds = + ScaleToOutsidePixelsOffset(groupBounds, scale.width, scale.height, + appUnitsPerDevPixel, residualOffset); + + const nsRect& untransformedPaintRect = + aWrappingItem->GetUntransformedPaintRect(); + + auto visibleRect = ScaleToOutsidePixelsOffset( + untransformedPaintRect, scale.width, scale.height, + 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("Inherrited scale %f %f\n", scale.width, scale.height); + GP("Bounds: %d %d %d %d vs %d %d %d %d\n", p.x, p.y, p.width, p.height, q.x, + q.y, q.width, q.height); + + 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); + } + // The bounds have changed so we need to discard the old image and add all + // the commands again. + auto p = group.mGroupBounds; + auto q = groupBounds; + if (!group.mGroupBounds.IsEqualEdges(groupBounds)) { + GP(" Bounds change: %d %d %d %d -> %d %d %d %d\n", p.x, p.y, p.width, + p.height, q.x, q.y, q.width, q.height); + } + + if (group.mScale != scale) { + GP(" Scale %f %f -> %f %f\n", group.mScale.width, group.mScale.height, + scale.width, scale.height); + } + + if (group.mResidualOffset != residualOffset) { + GP(" Residual Offset %f %f -> %f %f\n", group.mResidualOffset.x, + group.mResidualOffset.y, residualOffset.x, residualOffset.y); + } + + 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.mGroupBounds = groupBounds; + group.mLayerBounds = layerBounds; + group.mVisibleRect = visibleRect; + group.mActualBounds = IntRect(); + group.mPreservedRect = + group.mVisibleRect.Intersect(group.mLastVisibleRect).ToUnknownRect(); + group.mAppUnitsPerDevPixel = appUnitsPerDevPixel; + group.mClippedImageBounds = layerBounds.ToUnknownRect(); + + g.mTransform = Matrix::Scaling(scale.width, scale.height) + .PostTranslate(residualOffset.x, residualOffset.y); + group.mScale = scale; + group.mScrollId = scrollId; + g.ConstructGroups(aDisplayListBuilder, this, aBuilder, aResources, &group, + aList, 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(); + mLastLocalCanvasDatas.Clear(); + ClearCachedResources(); +} + +void WebRenderCommandBuilder::EmptyTransaction() { + // We need to update canvases that might have changed. + for (auto iter = mLastCanvasDatas.Iter(); !iter.Done(); iter.Next()) { + RefPtr<WebRenderCanvasData> canvasData = iter.Get()->GetKey(); + WebRenderCanvasRendererAsync* canvas = canvasData->GetCanvasRenderer(); + if (canvas) { + canvas->UpdateCompositableClientForEmptyTransaction(); + } + } + for (auto iter = mLastLocalCanvasDatas.Iter(); !iter.Done(); iter.Next()) { + RefPtr<WebRenderLocalCanvasData> canvasData = iter.Get()->GetKey(); + canvasData->RefreshExternalImage(); + canvasData->RequestFrameReadback(); + } +} + +bool WebRenderCommandBuilder::NeedsEmptyTransaction() { + return !mLastCanvasDatas.IsEmpty() || !mLastLocalCanvasDatas.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); + mBuilderDumpIndex = 0; + mLastCanvasDatas.Clear(); + mLastLocalCanvasDatas.Clear(); + mLastAsr = nullptr; + mContainsSVGGroup = false; + MOZ_ASSERT(mDumpIndent == 0); + + { + wr::StackingContextParams params; + 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, ContainerLayerParameters(), 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.crbegin(); it != mLayerScrollData.crend(); + it++) { + aScrollData.AddLayerData(*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_dl_dump_parent()) || + (XRE_IsContentProcess() && + StaticPrefs::gfx_webrender_dl_dump_content())); +} + +void WebRenderCommandBuilder::CreateWebRenderCommands( + nsDisplayItem* aItem, mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + nsDisplayListBuilder* aDisplayListBuilder) { + 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; + } + + aItem->SetPaintRect(aItem->GetBuildingRect()); + 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); + } +} + +void WebRenderCommandBuilder::CreateWebRenderCommandsFromDisplayList( + nsDisplayList* aDisplayList, nsDisplayItem* aWrappingItem, + nsDisplayListBuilder* aDisplayListBuilder, const StackingContextHelper& aSc, + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources) { + 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()); + } + + mDumpIndent++; + mClipManager.BeginList(aSc); + + bool apzEnabled = mManager->AsyncPanZoomEnabled(); + + FlattenedDisplayListIterator iter(aDisplayListBuilder, aDisplayList); + while (iter.HasNext()) { + nsDisplayItem* item = iter.GetNextItem(); + + DisplayItemType itemType = item->GetType(); + + // 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); + } + } + } + + bool forceNewLayerData = false; + size_t layerCountBeforeRecursing = mLayerScrollData.size(); + 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. + forceNewLayerData = item->UpdateScrollData(nullptr, nullptr); + + // 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; + forceNewLayerData = true; + } + + // 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 (!forceNewLayerData && + item->GetType() == DisplayItemType::TYPE_TRANSFORM && + aSc.GetDeferredTransformItem() && + (*aSc.GetDeferredTransformItem())->GetActiveScrolledRoot() != asr) { + forceNewLayerData = true; + } + + // 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 (forceNewLayerData) { + mAsrStack.push_back(asr); + } + } + + // This is where we emulate the clip/scroll stack that was previously + // implemented on the WR display list side. + auto spaceAndClipChain = mClipManager.SwitchItem(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 (forceNewLayerData) { + // 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(); + const ActiveScrolledRoot* stopAtAsr = + mAsrStack.empty() ? nullptr : mAsrStack.back(); + + int32_t descendants = + mLayerScrollData.size() - layerCountBeforeRecursing; + + // 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. + Maybe<nsDisplayTransform*> deferred = aSc.GetDeferredTransformItem(); + if (deferred && (*deferred)->GetActiveScrolledRoot() != + item->GetActiveScrolledRoot()) { + // 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()); + + // 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()); + } 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, + aSc.GetDeferredTransformMatrix()); + } + } + } + } + + mDumpIndent--; + 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); +} + +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, 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) { + mozilla::wr::ImageRendering rendering = wr::ToImageRendering( + nsLayoutUtils::GetSamplingFilterForFrame(aItem->Frame())); + 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(), rendering, key.value()); + + return true; +} + +bool BuildLayer(nsDisplayItem* aItem, BlobItemData* aData, + nsDisplayListBuilder* aDisplayListBuilder, + const gfx::Size& aScale) { + if (!aData->mLayerManager) { + aData->mLayerManager = + new BasicLayerManager(BasicLayerManager::BLM_INACTIVE); + } + RefPtr<BasicLayerManager> blm = aData->mLayerManager; + UniquePtr<LayerProperties> props; + if (blm->GetRoot()) { + props = LayerProperties::CloneFrom(blm->GetRoot()); + } + FrameLayerBuilder* layerBuilder = new FrameLayerBuilder(); + layerBuilder->Init(aDisplayListBuilder, blm, nullptr, true); + layerBuilder->DidBeginRetainedLayerTransaction(blm); + + blm->BeginTransaction(); + bool isInvalidated = false; + + ContainerLayerParameters param(aScale.width, aScale.height); + RefPtr<Layer> root = aItem->AsPaintedDisplayItem()->BuildLayer( + aDisplayListBuilder, blm, param); + + if (root) { + blm->SetRoot(root); + layerBuilder->WillEndTransaction(); + + // Check if there is any invalidation region. + nsIntRegion invalid; + if (props) { + props->ComputeDifferences(root, invalid, nullptr); + if (!invalid.IsEmpty()) { + isInvalidated = true; + } + } else { + isInvalidated = true; + } + } + blm->AbortTransaction(); + + return isInvalidated; +} + +static bool PaintByLayer(nsDisplayItem* aItem, + nsDisplayListBuilder* aDisplayListBuilder, + const RefPtr<BasicLayerManager>& aManager, + gfxContext* aContext, const gfx::Size& aScale, + const std::function<void()>& aPaintFunc) { + UniquePtr<LayerProperties> props; + if (aManager->GetRoot()) { + props = LayerProperties::CloneFrom(aManager->GetRoot()); + } + FrameLayerBuilder* layerBuilder = new FrameLayerBuilder(); + layerBuilder->Init(aDisplayListBuilder, aManager, nullptr, true); + layerBuilder->DidBeginRetainedLayerTransaction(aManager); + + aManager->SetDefaultTarget(aContext); + nsCString none; + aManager->BeginTransactionWithTarget(aContext, none); + bool isInvalidated = false; + + ContainerLayerParameters param(aScale.width, aScale.height); + RefPtr<Layer> root = aItem->AsPaintedDisplayItem()->BuildLayer( + aDisplayListBuilder, aManager, param); + + if (root) { + aManager->SetRoot(root); + layerBuilder->WillEndTransaction(); + + aPaintFunc(); + + // Check if there is any invalidation region. + nsIntRegion invalid; + if (props) { + props->ComputeDifferences(root, invalid, nullptr); + if (!invalid.IsEmpty()) { + isInvalidated = true; + } + } else { + isInvalidated = true; + } + } + +#ifdef MOZ_DUMP_PAINTING + if (gfxUtils::DumpDisplayList() || gfxEnv::DumpPaint()) { + fprintf_stderr( + gfxUtils::sDumpPaintFile, + "Basic layer tree for painting contents of display item %s(%p):\n", + aItem->Name(), aItem->Frame()); + std::stringstream stream; + aManager->Dump(stream, "", gfxEnv::DumpPaintToFile()); + fprint_stderr(gfxUtils::sDumpPaintFile, + stream); // not a typo, fprint_stderr declared in nsDebug.h + } +#endif + + if (aManager->InTransaction()) { + aManager->AbortTransaction(); + } + + aManager->SetTarget(nullptr); + aManager->SetDefaultTarget(nullptr); + + return isInvalidated; +} + +static bool PaintItemByDrawTarget(nsDisplayItem* aItem, gfx::DrawTarget* aDT, + const LayoutDevicePoint& aOffset, + const IntRect& visibleRect, + nsDisplayListBuilder* aDisplayListBuilder, + const RefPtr<BasicLayerManager>& aManager, + const gfx::Size& aScale, + Maybe<gfx::DeviceColor>& aHighlight) { + MOZ_ASSERT(aDT); + + bool isInvalidated = false; + // XXX Why is this ClearRect() needed? + aDT->ClearRect(Rect(visibleRect)); + RefPtr<gfxContext> context = gfxContext::CreateOrNull(aDT); + MOZ_ASSERT(context); + + 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; + } + case DisplayItemType::TYPE_FILTER: { + context->SetMatrix(context->CurrentMatrix() + .PreScale(aScale.width, aScale.height) + .PreTranslate(-aOffset.x, -aOffset.y)); + isInvalidated = PaintByLayer( + aItem, aDisplayListBuilder, aManager, context, {1, 1}, [&]() { + static_cast<nsDisplayFilters*>(aItem)->PaintAsLayer( + aDisplayListBuilder, context, aManager); + }); + break; + } + + default: + if (!aItem->AsPaintedDisplayItem()) { + break; + } + + context->SetMatrix(context->CurrentMatrix() + .PreScale(aScale.width, aScale.height) + .PreTranslate(-aOffset.x, -aOffset.y)); + if (aDisplayListBuilder->IsPaintingToWindow()) { + aItem->Frame()->AddStateBits(NS_FRAME_PAINTED_THEBES); + } + aItem->AsPaintedDisplayItem()->Paint(aDisplayListBuilder, context); + isInvalidated = true; + break; + } + + if (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. + if (aHighlight) { + aDT->SetTransform(gfx::Matrix()); + aDT->FillRect(Rect(visibleRect), gfx::ColorPattern(aHighlight.value())); + } + if (aItem->Frame()->PresContext()->GetPaintFlashing() && isInvalidated) { + aDT->SetTransform(gfx::Matrix()); + float r = float(rand()) / float(RAND_MAX); + float g = float(rand()) / float(RAND_MAX); + float b = float(rand()) / float(RAND_MAX); + aDT->FillRect(Rect(visibleRect), + gfx::ColorPattern(gfx::DeviceColor(r, g, b, 0.5))); + } + } + + return isInvalidated; +} + +// 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) { + const bool paintOnContentSide = aItem->MustPaintOnContentSide(); + bool useBlobImage = + StaticPrefs::gfx_webrender_blob_images() && !paintOnContentSide; + Maybe<gfx::DeviceColor> highlight = Nothing(); + if (StaticPrefs::gfx_webrender_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); + + bool snap; + nsRect itemBounds = aItem->GetBounds(aDisplayListBuilder, &snap); + + // 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. + nsRect paintBounds = (useBlobImage || paintOnContentSide) + ? itemBounds + : aItem->GetClippedBounds(aDisplayListBuilder); + + // nsDisplayItem::Paint() may refer the variables that come from + // ComputeVisibility(). So we should call ComputeVisibility() before painting. + // e.g.: nsDisplayBoxShadowInner uses mPaintRect in Paint() and mPaintRect is + // computed in nsDisplayBoxShadowInner::ComputeVisibility(). + nsRegion visibleRegion(paintBounds); + aItem->SetPaintRect(paintBounds); + aItem->ComputeVisibility(aDisplayListBuilder, &visibleRegion); + + const int32_t appUnitsPerDevPixel = + aItem->Frame()->PresContext()->AppUnitsPerDevPixel(); + auto bounds = + LayoutDeviceRect::FromAppUnits(paintBounds, appUnitsPerDevPixel); + if (bounds.IsEmpty()) { + return nullptr; + } + + gfx::Size scale = aSc.GetInheritedScale(); + gfx::Size oldScale = fallbackData->mScale; + // We tolerate slight changes in scale so that we don't, for example, + // rerasterize on MotionMark + bool differentScale = gfx::FuzzyEqual(scale.width, oldScale.width, 1e-6f) && + gfx::FuzzyEqual(scale.height, oldScale.height, 1e-6f); + + LayoutDeviceToLayerScale2D layerScale(scale.width, scale.height); + + auto trans = + ViewAs<LayerPixel>(aSc.GetSnappingSurfaceTransform().GetTranslation()); + 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 (opacity == wr::OpacityType::Opaque && snap) { + dtRect = LayerIntRect::FromUnknownRect( + ScaleToNearestPixelsOffset(paintBounds, scale.width, scale.height, + appUnitsPerDevPixel, residualOffset)); + + visibleRect = LayerIntRect::FromUnknownRect( + ScaleToNearestPixelsOffset( + aItem->GetBuildingRect(), scale.width, scale.height, + appUnitsPerDevPixel, residualOffset)) + .Intersect(dtRect); + } else { + dtRect = ScaleToOutsidePixelsOffset(paintBounds, scale.width, scale.height, + appUnitsPerDevPixel, residualOffset); + + visibleRect = ScaleToOutsidePixelsOffset( + aItem->GetBuildingRect(), scale.width, scale.height, + 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; + + // nsDisplayFilters is rendered via BasicLayerManager which means the + // invalidate region is unknown until we traverse the displaylist contained by + // it. + if (geometry && !fallbackData->IsInvalid() && + aItem->GetType() != DisplayItemType::TYPE_FILTER && + aItem->GetType() != DisplayItemType::TYPE_SVG_WRAPPER && differentScale) { + nsRect invalid; + nsRegion invalidRegion; + + if (aItem->IsInvalid(invalid)) { + invalidRegion.OrWith(paintBounds); + } else { + nsPoint shift = itemBounds.TopLeft() - geometry->mBounds.TopLeft(); + geometry->MoveBy(shift); + aItem->ComputeInvalidationRegion(aDisplayListBuilder, geometry, + &invalidRegion); + + nsRect lastBounds = fallbackData->mBounds; + lastBounds.MoveBy(shift); + + if (!lastBounds.IsEqualInterior(paintBounds)) { + invalidRegion.OrWith(lastBounds); + invalidRegion.OrWith(paintBounds); + } + } + needPaint = !invalidRegion.IsEmpty(); + } + + 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 (!fallbackData->mBasicLayerManager) { + fallbackData->mBasicLayerManager = + new BasicLayerManager(BasicLayerManager::BLM_INACTIVE); + } + bool isInvalidated = PaintItemByDrawTarget( + aItem, dt, (dtRect / layerScale).TopLeft(), + /*aVisibleRect: */ dt->GetRect(), aDisplayListBuilder, + fallbackData->mBasicLayerManager, scale, highlight); + if (!isInvalidated) { + if (!aItem->GetBuildingRect().IsEqualInterior( + fallbackData->mBuildingRect)) { + // The building rect has changed but we didn't see any invalidations. + // We should still consider this an invalidation. + isInvalidated = true; + } + } + + // 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; + } + + if (isInvalidated) { + 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 { + // If there is no invalidation region and we don't have a image key, + // it means we don't need to push image for the item. + if (!fallbackData->GetBlobImageKey().isSome()) { + return nullptr; + } + } + } else { + WebRenderImageData* imageData = fallbackData->PaintIntoImage(); + + imageData->CreateImageClientIfNeeded(); + RefPtr<ImageClient> imageClient = imageData->GetImageClient(); + RefPtr<ImageContainer> imageContainer = + LayerManager::CreateImageContainer(); + bool isInvalidated = false; + + { + UpdateImageHelper helper(imageContainer, imageClient, + dtRect.Size().ToUnknownSize(), format); + { + RefPtr<gfx::DrawTarget> dt = helper.GetDrawTarget(); + if (!dt) { + return nullptr; + } + if (!fallbackData->mBasicLayerManager) { + fallbackData->mBasicLayerManager = + new BasicLayerManager(mManager->GetWidget()); + } + isInvalidated = PaintItemByDrawTarget( + aItem, dt, + /*aOffset: */ aImageRect.TopLeft(), + /*aVisibleRect: */ dt->GetRect(), aDisplayListBuilder, + fallbackData->mBasicLayerManager, scale, highlight); + } + + if (isInvalidated) { + // Update image if there it's invalidated. + if (!helper.UpdateImage()) { + return nullptr; + } + } else { + // If there is no invalidation region and we don't have a image key, + // it means we don't need to push image for the item. + if (!imageData->GetImageKey().isSome()) { + 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 (isInvalidated && + !imageData->UpdateImageKey(imageContainer, aResources, true)) { + return nullptr; + } + } + + fallbackData->mScale = scale; + fallbackData->SetInvalid(false); + } + + if (useBlobImage) { + aResources.SetBlobImageVisibleArea( + fallbackData->GetBlobImageKey().value(), + ViewAs<ImagePixel>(visibleRect, PixelCastJustification::LayerIsImage)); + } + + // Update current bounds to fallback data + fallbackData->mBounds = paintBounds; + fallbackData->mBuildingRect = aItem->GetBuildingRect(); + + MOZ_ASSERT(fallbackData->GetImageKey()); + + return fallbackData.forget(); +} + +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() { + if (mBlobKey) { + mManager->AddBlobImageKeyForDiscard(mBlobKey.value()); + } + mBlobKey.reset(); + } + + UserDataType GetType() override { return UserDataType::eMask; } + static UserDataType Type() { return UserDataType::eMask; } + + Maybe<wr::BlobImageKey> mBlobKey; + std::vector<RefPtr<gfx::ScaledFont>> mFonts; + std::vector<RefPtr<gfx::SourceSurface>> mExternalSurfaces; + LayerIntRect mItemRect; + nsPoint mMaskOffset; + nsStyleImageLayers mMaskStyle; + gfx::Size mScale; + bool mShouldHandleOpacity; +}; + +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(); + + Size scale = aSc.GetInheritedScale(); + Size 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.width, oldScale.width, 1e-6f) && + FuzzyEqual(scale.height, oldScale.height, 1e-6f); + + LayerIntRect itemRect = + LayerIntRect::FromUnknownRect(bounds.ScaleToOutsidePixels( + scale.width, scale.height, appUnitsPerDevPixel)); + + if (itemRect.IsEmpty()) { + return Nothing(); + } + + LayoutDeviceToLayerScale2D layerScale(scale.width, scale.height); + LayoutDeviceRect imageRect = LayerRect(itemRect) / layerScale; + + nsPoint maskOffset = aMaskItem->ToReferenceFrame() - bounds.TopLeft(); + + 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 || + aMaskItem->ShouldHandleOpacity() != maskData->mShouldHandleOpacity) { + IntSize size = itemRect.Size().ToUnknownSize(); + + 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)); + + RefPtr<gfxContext> context = gfxContext::CreateOrNull(dt); + MOZ_ASSERT(context); + + context->SetMatrix(context->CurrentMatrix() + .PreTranslate(-itemRect.x, -itemRect.y) + .PreScale(scale.width, scale.height)); + + bool maskPainted = false; + bool maskIsComplete = + aMaskItem->PaintMask(aDisplayListBuilder, context, &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()->GetStateBits() & 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 = aMaskItem->ShouldHandleOpacity(); + } + } + + wr::ImageMask imageMask; + imageMask.image = wr::AsImageKey(maskData->mBlobKey.value()); + imageMask.rect = wr::ToLayoutRect(imageRect); + imageMask.repeat = false; + 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); + gfx::SamplingFilter sampleFilter = + nsLayoutUtils::GetSamplingFilterForFrame(aItem->Frame()); + aBuilder.PushImage(dest, dest, !aItem->BackfaceIsHidden(), + wr::ToImageRendering(sampleFilter), + fallbackData->GetImageKey().value()); + return true; +} + +void WebRenderCommandBuilder::RemoveUnusedAndResetWebRenderUserData() { + for (auto iter = mWebRenderUserDatas.Iter(); !iter.Done(); iter.Next()) { + WebRenderUserData* data = iter.Get()->GetKey(); + 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.RemoveEntry(data->AsCanvasData()); + break; + case WebRenderUserData::UserDataType::eLocalCanvas: + mLastLocalCanvasDatas.RemoveEntry(data->AsLocalCanvasData()); + break; + case WebRenderUserData::UserDataType::eAnimation: + EffectCompositor::ClearIsRunningOnCompositor( + frame, GetDisplayItemTypeFromKey(data->GetDisplayItemKey())); + break; + default: + break; + } + + iter.Remove(); + continue; + } + + data->SetUsed(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..22ebac8d4f --- /dev/null +++ b/gfx/layers/wr/WebRenderCommandBuilder.h @@ -0,0 +1,215 @@ +/* -*- 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/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 "DisplayItemCache.h" + +namespace mozilla { + +namespace layers { + +class CanvasLayer; +class ImageClient; +class ImageContainer; +class WebRenderBridgeChild; +class WebRenderCanvasData; +class WebRenderCanvasRendererAsync; +class WebRenderImageData; +class WebRenderFallbackData; +class WebRenderParentCommand; +class WebRenderUserData; + +class WebRenderCommandBuilder final { + typedef nsTHashtable<nsRefPtrHashKey<WebRenderUserData>> + WebRenderUserDataRefTable; + typedef nsTHashtable<nsRefPtrHashKey<WebRenderCanvasData>> CanvasDataSet; + typedef nsTHashtable<nsRefPtrHashKey<WebRenderLocalCanvasData>> + LocalCanvasDataSet; + + 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); + + 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); + + 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); + + // 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->GetOrInsert( + WebRenderUserDataKey(aItem->GetPerFrameKey(), T::Type())); + if (!data) { + data = new T(GetRenderRootStateManager(), aItem); + mWebRenderUserDatas.PutEntry(data); + if (aOutIsRecycled) { + *aOutIsRecycled = false; + } + } + + 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.PutEntry(data->AsCanvasData()); + break; + case WebRenderUserData::UserDataType::eLocalCanvas: + mLastLocalCanvasDatas.PutEntry(data->AsLocalCanvasData()); + 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); + + ClipManager mClipManager; + + // 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; + const ActiveScrolledRoot* mLastAsr; + + WebRenderUserDataRefTable mWebRenderUserDatas; + + // Store of WebRenderCanvasData objects for use in empty transactions + CanvasDataSet mLastCanvasDatas; + // Store of WebRenderLocalCanvasData objects for use in empty transactions + LocalCanvasDataSet mLastLocalCanvasDatas; + + 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..b08ff5b959 --- /dev/null +++ b/gfx/layers/wr/WebRenderDrawEventRecorder.h @@ -0,0 +1,49 @@ +/* -*- 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) {} + + 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..b3e0bf627d --- /dev/null +++ b/gfx/layers/wr/WebRenderImageHost.cpp @@ -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/. */ + +#include "WebRenderImageHost.h" + +#include <utility> + +#include "mozilla/ScopeExit.h" +#include "mozilla/layers/AsyncImagePipelineManager.h" +#include "mozilla/layers/Compositor.h" // for Compositor +#include "mozilla/layers/CompositorVsyncScheduler.h" // for CompositorVsyncScheduler +#include "mozilla/layers/Effects.h" // for TexturedEffect, Effect, etc +#include "mozilla/layers/LayerManagerComposite.h" // for TexturedEffect, Effect, etc +#include "mozilla/layers/WebRenderBridgeParent.h" +#include "mozilla/layers/WebRenderTextureHost.h" +#include "nsAString.h" +#include "nsDebug.h" // for NS_WARNING, NS_ASSERTION +#include "nsPrintfCString.h" // for nsPrintfCString +#include "nsString.h" // for nsAutoCString + +namespace mozilla { + +using namespace gfx; + +namespace layers { + +class ISurfaceAllocator; + +WebRenderImageHost::WebRenderImageHost(const TextureInfo& aTextureInfo) + : CompositableHost(aTextureInfo), + ImageComposite(), + mCurrentAsyncImageManager(nullptr) {} + +WebRenderImageHost::~WebRenderImageHost() { MOZ_ASSERT(mWrBridges.empty()); } + +void WebRenderImageHost::UseTextureHost( + const nsTArray<TimedTexture>& aTextures) { + CompositableHost::UseTextureHost(aTextures); + MOZ_ASSERT(aTextures.Length() >= 1); + + 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); + img.mTextureHost->Updated(); + } + + SetImages(std::move(newImages)); + + if (GetAsyncRef()) { + for (const auto& it : mWrBridges) { + RefPtr<WebRenderBridgeParent> wrBridge = it.second->WrBridge(); + if (wrBridge && wrBridge->CompositorScheduler()) { + wrBridge->CompositorScheduler()->ScheduleComposition(); + } + } + } + + // 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::UseComponentAlphaTextures( + TextureHost* aTextureOnBlack, TextureHost* aTextureOnWhite) { + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); +} + +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::GetAsTextureHost(IntRect* aPictureRect) { + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); + return nullptr; +} + +TextureHost* WebRenderImageHost::GetAsTextureHostForComposite( + AsyncImagePipelineManager* aAsyncImageManager) { + 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); + SetCurrentTextureHost(img->mTextureHost); + + 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::Attach(Layer* aLayer, TextureSourceProvider* aProvider, + AttachFlags aFlags) {} + +void WebRenderImageHost::Composite( + Compositor* aCompositor, LayerComposite* aLayer, EffectChain& aEffectChain, + float aOpacity, const gfx::Matrix4x4& aTransform, + const gfx::SamplingFilter aSamplingFilter, const gfx::IntRect& aClipRect, + const nsIntRegion* aVisibleRegion, const Maybe<gfx::Polygon>& aGeometry) { + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); +} + +void WebRenderImageHost::SetTextureSourceProvider( + TextureSourceProvider* aProvider) { + if (mTextureSourceProvider != aProvider) { + for (const auto& img : Images()) { + img.mTextureHost->SetTextureSourceProvider(aProvider); + } + } + CompositableHost::SetTextureSourceProvider(aProvider); +} + +void WebRenderImageHost::PrintInfo(std::stringstream& aStream, + const char* aPrefix) { + aStream << aPrefix; + aStream << nsPrintfCString("WebRenderImageHost (0x%p)", this).get(); + + nsAutoCString pfx(aPrefix); + pfx += " "; + for (const auto& img : Images()) { + aStream << "\n"; + img.mTextureHost->PrintInfo(aStream, pfx.get()); + aStream << " [picture-rect=" << img.mPictureRect << "]"; + } +} + +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> " : " "); + } +} + +already_AddRefed<gfx::DataSourceSurface> WebRenderImageHost::GetAsSurface() { + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); + return nullptr; +} + +bool WebRenderImageHost::Lock() { + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); + return false; +} + +void WebRenderImageHost::Unlock() { + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); +} + +IntSize WebRenderImageHost::GetImageSize() { + const TimedImage* img = ChooseImage(); + if (img) { + return IntSize(img->mPictureRect.Width(), img->mPictureRect.Height()); + } + return IntSize(); +} + +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..f2fb463ff9 --- /dev/null +++ b/gfx/layers/wr/WebRenderImageHost.h @@ -0,0 +1,102 @@ +/* -*- 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 <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 WebRenderBridgeParent; +class WebRenderBridgeParentRef; + +/** + * ImageHost. Works with ImageClientSingle and ImageClientBuffered + */ +class WebRenderImageHost : public CompositableHost, public ImageComposite { + public: + explicit WebRenderImageHost(const TextureInfo& aTextureInfo); + virtual ~WebRenderImageHost(); + + CompositableType GetType() override { return mTextureInfo.mCompositableType; } + + void Composite(Compositor* aCompositor, LayerComposite* aLayer, + EffectChain& aEffectChain, float aOpacity, + const gfx::Matrix4x4& aTransform, + const gfx::SamplingFilter aSamplingFilter, + const gfx::IntRect& aClipRect, + const nsIntRegion* aVisibleRegion = nullptr, + const Maybe<gfx::Polygon>& aGeometry = Nothing()) override; + + void UseTextureHost(const nsTArray<TimedTexture>& aTextures) override; + void UseComponentAlphaTextures(TextureHost* aTextureOnBlack, + TextureHost* aTextureOnWhite) override; + void RemoveTextureHost(TextureHost* aTexture) override; + + TextureHost* GetAsTextureHost(gfx::IntRect* aPictureRect = nullptr) override; + + void Attach(Layer* aLayer, TextureSourceProvider* aProvider, + AttachFlags aFlags = NO_FLAGS) override; + + void SetTextureSourceProvider(TextureSourceProvider* aProvider) override; + + gfx::IntSize GetImageSize() override; + + void PrintInfo(std::stringstream& aStream, const char* aPrefix) override; + + void Dump(std::stringstream& aStream, const char* aPrefix = "", + bool aDumpHtml = false) override; + + already_AddRefed<gfx::DataSourceSurface> GetAsSurface() override; + + bool Lock() override; + + void Unlock() override; + + void CleanupResources() override; + + uint32_t GetDroppedFrames() override { return GetDroppedFramesAndReset(); } + + WebRenderImageHost* AsWebRenderImageHost() override { return this; } + + 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; +}; + +} // 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..b0011e24de --- /dev/null +++ b/gfx/layers/wr/WebRenderLayerManager.cpp @@ -0,0 +1,772 @@ +/* -*- 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 "BasicLayers.h" +#include "Layers.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/gfx/gfxVars.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 "nsDisplayList.h" +#include "nsLayoutUtils.h" +#include "WebRenderCanvasRenderer.h" + +#ifdef XP_WIN +# include "gfxDWriteFonts.h" +#endif + +namespace mozilla { + +using namespace gfx; + +namespace layers { + +WebRenderLayerManager::WebRenderLayerManager(nsIWidget* aWidget) + : mWidget(aWidget), + mLatestTransactionId{0}, + mNeedsComposite(false), + mIsFirstPaint(false), + mTarget(nullptr), + mPaintSequenceNumber(0), + mWebRenderCommandBuilder(this), + mLastDisplayListSize(0) { + 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->WindowType() != eWindowType_popup) { + windowKind = WindowKind::MAIN; + } else { + windowKind = WindowKind::SECONDARY; + } + + LayoutDeviceIntSize size = mWidget->GetClientSize(); + 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; + } + + TextureFactoryIdentifier textureFactoryIdentifier; + wr::MaybeIdNamespace idNamespace; + // Sync ipc + if (!bridge->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."; + aError.Append(hasInitialized ? "_POST"_ns : "_FIRST"_ns); + return false; + } + + mWrChild = static_cast<WebRenderBridgeChild*>(bridge); + WrBridge()->SetWebRenderLayerManager(this); + WrBridge()->IdentifyTextureHost(textureFactoryIdentifier); + WrBridge()->SetNamespace(idNamespace.ref()); + *aTextureFactoryIdentifier = textureFactoryIdentifier; + hasInitialized = true; + return true; +} + +void WebRenderLayerManager::Destroy() { DoDestroy(/* aIsSync */ false); } + +void WebRenderLayerManager::DoDestroy(bool aIsSync) { + MOZ_ASSERT(NS_IsMainThread()); + + if (IsDestroyed()) { + return; + } + + LayerManager::Destroy(); + + 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->NotifyTransactionCompleted(id); + }); + NS_DispatchToMainThread(task.forget()); + } + + // Forget the widget pointer in case we outlive our owning widget. + mWidget = nullptr; +} + +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()->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::PayloadPresented(const TimeStamp& aTimeStamp) { + MOZ_CRASH("WebRenderLayerManager::PayloadPresented should not be called"); +} + +void WebRenderLayerManager::TakeCompositionPayloads( + nsTArray<CompositionPayload>& aPayloads) { + aPayloads.Clear(); + + std::swap(mPayload, aPayloads); +} + +bool WebRenderLayerManager::BeginTransactionWithTarget(gfxContext* aTarget, + const nsCString& aURL) { + mTarget = aTarget; + return BeginTransaction(aURL); +} + +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) { + // 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(); + + // Since we don't do repeat transactions right now, just set the time + mAnimationReadyTime = TimeStamp::Now(); + + mLatestTransactionId = + mTransactionIdAllocator->GetTransactionId(/*aThrottle*/ true); + + 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 (auto it = transactionData->mScrollUpdates.Iter(); !it.Done(); + it.Next()) { + nsLayoutUtils::NotifyPaintSkipTransaction(/*scroll id=*/it.Key()); + } + } + + 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::EndTransaction(DrawPaintedLayerCallback aCallback, + void* aCallbackData, + EndTransactionFlags aFlags) { + // This should never get called, all callers should use + // EndTransactionWithoutLayer instead. + MOZ_ASSERT(false); +} + +void WebRenderLayerManager::EndTransactionWithoutLayer( + nsDisplayList* aDisplayList, nsDisplayListBuilder* aDisplayListBuilder, + WrFiltersHolder&& aFilters, WebRenderBackgroundData* aBackground) { + AUTO_PROFILER_TRACING_MARKER("Paint", "RenderLayers", GRAPHICS); + + // Since we don't do repeat transactions right now, just set the time + mAnimationReadyTime = TimeStamp::Now(); + + WrBridge()->BeginTransaction(); + + LayoutDeviceIntSize size = mWidget->GetClientSize(); + + // While the first display list after tab-switch can be large, the + // following ones are always smaller thanks to interning (rarely above 0.3MB). + // So don't let the spike of the first allocation make us allocate a large + // contiguous buffer (with some likelihood of OOM, see bug 1531819). + static const size_t kMaxPrealloc = 300000; + size_t preallocate = + mLastDisplayListSize < kMaxPrealloc ? mLastDisplayListSize : kMaxPrealloc; + + wr::DisplayListBuilder builder(WrBridge()->GetPipeline(), preallocate, + &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_dl_dump_content_serialized()) { + builder.DumpSerializedDisplayList(); + } + + if (aDisplayList) { + MOZ_ASSERT(aDisplayListBuilder && !aBackground); + // Record the time spent "layerizing". WR doesn't actually layerize but + // generating the WR display list is the closest equivalent + PaintTelemetry::AutoRecord record(PaintTelemetry::Metric::Layerization); + + mDisplayItemCache.SetDisplayList(aDisplayListBuilder, aDisplayList); + + mWebRenderCommandBuilder.BuildWebRenderCommands( + builder, 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(builder); + if (dumpEnabled) { + printf_stderr("(no display list; background only)\n"); + builderDumpIndex = + builder.Dump(/*indent*/ 1, Some(builderDumpIndex), Nothing()); + } + } + + mWidget->AddWindowOverlayWebRenderCommands(WrBridge(), builder, + resourceUpdates); + if (dumpEnabled) { + printf_stderr("(window overlay)\n"); + Unused << builder.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); + } + + mLatestTransactionId = + mTransactionIdAllocator->GetTransactionId(/*aThrottle*/ true); + + // 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; + builder.Finalize(dlData); + mLastDisplayListSize = dlData.mDL->mCapacity; + resourceUpdates.Flush(dlData.mResourceUpdates, dlData.mSmallShmems, + dlData.mLargeShmems); + dlData.mRect = + LayoutDeviceRect(LayoutDevicePoint(), LayoutDeviceSize(size)); + dlData.mScrollData.emplace(std::move(mScrollData)); + + 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(); +} + +void WebRenderLayerManager::MakeSnapshotIfRequired(LayoutDeviceIntSize aSize) { + if (!mTarget || 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; + } + + texture->InitIPDLActor(WrBridge()); + if (!texture->GetIPDLActor()) { + return; + } + + IntRect bounds = ToOutsideIntRect(mTarget->GetClipExtents()); + bool needsYFlip = false; + if (!WrBridge()->SendGetSnapshot(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::SetLayersObserverEpoch(LayersObserverEpoch aEpoch) { + if (WrBridge()->IPCOpen()) { + WrBridge()->SendSetLayersObserverEpoch(aEpoch); + } +} + +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); + } + } + + // These observers fire whether or not we were in a transaction. + for (size_t i = 0; i < mDidCompositeObservers.Length(); i++) { + mDidCompositeObservers[i]->DidComposite(); + } +} + +void WebRenderLayerManager::ClearCachedResources(Layer* aSubtree) { + if (!WrBridge()->IPCOpen()) { + gfxCriticalNote << "IPC Channel is already torn down unexpectedly\n"; + return; + } + WrBridge()->BeginClearCachedResources(); + mWebRenderCommandBuilder.ClearCachedResources(); + DiscardImages(); + mStateManager.ClearCachedResources(); + WrBridge()->EndClearCachedResources(); +} + +void WebRenderLayerManager::WrUpdated() { + ClearAsyncAnimations(); + 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::AddDidCompositeObserver( + DidCompositeObserver* aObserver) { + if (!mDidCompositeObservers.Contains(aObserver)) { + mDidCompositeObservers.AppendElement(aObserver); + } +} + +void WebRenderLayerManager::RemoveDidCompositeObserver( + DidCompositeObserver* aObserver) { + mDidCompositeObservers.RemoveElement(aObserver); +} + +void WebRenderLayerManager::FlushRendering() { + 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); + + // Limit async FlushRendering to !resizing and Win DComp. + // XXX relax the limitation + if (WrBridge()->GetCompositorUseDComp() && !resizing) { + cBridge->SendFlushRenderingAsync(); + } else if (mWidget->SynchronouslyRepaintOnResize() || + StaticPrefs::layers_force_synchronous_resize()) { + cBridge->SendFlushRendering(); + } else { + cBridge->SendFlushRenderingAsync(); + } +} + +void WebRenderLayerManager::WaitOnTransactionProcessed() { + CompositorBridgeChild* bridge = GetCompositorBridgeChild(); + if (bridge) { + bridge->SendWaitOnTransactionProcessed(); + } +} + +void WebRenderLayerManager::SendInvalidRegion(const nsIntRegion& aRegion) { + // XXX Webrender does not support invalid region yet. + +#ifdef XP_WIN + // When DWM is disabled, each window does not have own back buffer. They would + // paint directly to a buffer that was to be displayed by the video card. + // WM_PAINT via SendInvalidRegion() requests necessary re-paint. + const bool needsInvalidate = !gfx::gfxVars::DwmCompositionEnabled(); +#else + const bool needsInvalidate = true; +#endif + if (needsInvalidate && WrBridge()) { + WrBridge()->SendInvalidateRenderedFrame(); + } +} + +void WebRenderLayerManager::ScheduleComposite() { + WrBridge()->SendScheduleComposite(); +} + +void WebRenderLayerManager::SetRoot(Layer* aLayer) { + // This should never get called + MOZ_ASSERT(false); +} + +already_AddRefed<PersistentBufferProvider> +WebRenderLayerManager::CreatePersistentBufferProvider( + const gfx::IntSize& aSize, gfx::SurfaceFormat aFormat) { + // Ensure devices initialization for canvas 2d. The devices are lazily + // initialized with WebRender to reduce memory usage. + gfxPlatform::GetPlatform()->EnsureDevicesInitialized(); + + RefPtr<PersistentBufferProvider> provider = + PersistentBufferProviderShared::Create(aSize, aFormat, + AsKnowsCompositor()); + if (provider) { + return provider.forget(); + } + + return LayerManager::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); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/wr/WebRenderLayerManager.h b/gfx/layers/wr/WebRenderLayerManager.h new file mode 100644 index 0000000000..ef7bcc1931 --- /dev/null +++ b/gfx/layers/wr/WebRenderLayerManager.h @@ -0,0 +1,247 @@ +/* -*- 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/LayerManager.h" // for DidCompositeObserver (ptr only), LayerManager::END_DEFAULT, LayerManager::En... +#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 "nsHashKeys.h" // for nsRefPtrHashKey +#include "nsRegion.h" // for nsIntRegion +#include "nsStringFwd.h" // for nsCString, nsAString +#include "nsTArray.h" // for nsTArray +#include "nsTHashtable.h" // for nsTHashtable<>::Iterator, nsTHashtable + +class gfxContext; +class nsDisplayList; +class nsDisplayListBuilder; +class nsIWidget; + +namespace mozilla { + +struct ActiveScrolledRoot; + +namespace layers { + +class CompositorBridgeChild; +class KnowsCompositor; +class Layer; +class PCompositorBridgeChild; +class WebRenderBridgeChild; +class WebRenderParentCommand; + +class WebRenderLayerManager final : public LayerManager { + typedef nsTArray<RefPtr<Layer>> LayerRefArray; + typedef nsTHashtable<nsRefPtrHashKey<WebRenderUserData>> + WebRenderUserDataRefTable; + + public: + explicit WebRenderLayerManager(nsIWidget* aWidget); + bool Initialize(PCompositorBridgeChild* aCBChild, wr::PipelineId aLayersId, + TextureFactoryIdentifier* aTextureFactoryIdentifier, + nsCString& aError); + + void Destroy() override; + + void DoDestroy(bool aIsSync); + + protected: + virtual ~WebRenderLayerManager(); + + public: + KnowsCompositor* AsKnowsCompositor() override; + WebRenderLayerManager* AsWebRenderLayerManager() 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) override; + bool BeginTransaction(const nsCString& aURL) override; + bool EndEmptyTransaction(EndTransactionFlags aFlags = END_DEFAULT) override; + void EndTransactionWithoutLayer( + nsDisplayList* aDisplayList, nsDisplayListBuilder* aDisplayListBuilder, + WrFiltersHolder&& aFilters = WrFiltersHolder(), + WebRenderBackgroundData* aBackground = nullptr); + void EndTransaction(DrawPaintedLayerCallback aCallback, void* aCallbackData, + EndTransactionFlags aFlags = END_DEFAULT) override; + + LayersBackend GetBackendType() override { return LayersBackend::LAYERS_WR; } + void GetBackendName(nsAString& name) override; + const char* Name() const override { return "WebRender"; } + + void SetRoot(Layer* aLayer) override; + + already_AddRefed<PaintedLayer> CreatePaintedLayer() override { + return nullptr; + } + already_AddRefed<ContainerLayer> CreateContainerLayer() override { + return nullptr; + } + already_AddRefed<ImageLayer> CreateImageLayer() override { return nullptr; } + already_AddRefed<ColorLayer> CreateColorLayer() override { return nullptr; } + already_AddRefed<CanvasLayer> CreateCanvasLayer() override { return nullptr; } + + bool NeedsWidgetInvalidation() override { return false; } + + void SetLayersObserverEpoch(LayersObserverEpoch aEpoch) override; + + void DidComposite(TransactionId aTransactionId, + const mozilla::TimeStamp& aCompositeStart, + const mozilla::TimeStamp& aCompositeEnd) override; + + void ClearCachedResources(Layer* aSubtree = nullptr) override; + void UpdateTextureFactoryIdentifier( + const TextureFactoryIdentifier& aNewIdentifier) override; + TextureFactoryIdentifier GetTextureFactoryIdentifier() override; + + void SetTransactionIdAllocator(TransactionIdAllocator* aAllocator) override; + TransactionId GetLastTransactionId() override; + + void AddDidCompositeObserver(DidCompositeObserver* aObserver) override; + void RemoveDidCompositeObserver(DidCompositeObserver* aObserver) override; + + void FlushRendering() override; + void WaitOnTransactionProcessed() override; + + void SendInvalidRegion(const nsIntRegion& aRegion) override; + + void ScheduleComposite() override; + + void SetNeedsComposite(bool aNeedsComposite) override { + mNeedsComposite = aNeedsComposite; + } + bool NeedsComposite() const override { return mNeedsComposite; } + void SetIsFirstPaint() override { mIsFirstPaint = true; } + bool GetIsFirstPaint() const override { return mIsFirstPaint; } + void SetFocusTarget(const FocusTarget& aFocusTarget) override; + + already_AddRefed<PersistentBufferProvider> CreatePersistentBufferProvider( + const gfx::IntSize& aSize, gfx::SurfaceFormat aFormat) override; + + bool AsyncPanZoomEnabled() const override; + + // 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; } + + virtual void PayloadPresented(const TimeStamp& aTimeStamp) override; + + void TakeCompositionPayloads(nsTArray<CompositionPayload>& aPayloads); + + void GetFrameUniformity(FrameUniformityData* aOutData) override; + + 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; + + nsTArray<DidCompositeObserver*> mDidCompositeObservers; + + // 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; + FocusTarget mFocusTarget; + + // 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. + RefPtr<gfxContext> mTarget; + + // See equivalent field in ClientLayerManager + uint32_t mPaintSequenceNumber; + // See equivalent field in ClientLayerManager + APZTestData mApzTestData; + + TimeStamp mTransactionStart; + nsCString mURL; + WebRenderCommandBuilder mWebRenderCommandBuilder; + + size_t mLastDisplayListSize; + RenderRootStateManager mStateManager; + DisplayItemCache mDisplayItemCache; +}; + +} // 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..ac30e5e4d3 --- /dev/null +++ b/gfx/layers/wr/WebRenderMessageUtils.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_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(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mLength); + aMsg->WriteBytes(aParam.mData, aParam.mLength); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + size_t length; + return ReadParam(aMsg, aIter, &length) && aResult->Allocate(length) && + aMsg->ReadBytesInto(aIter, aResult->mData, length); + } +}; + +template <> +struct ParamTraits<mozilla::wr::ImageDescriptor> { + typedef mozilla::wr::ImageDescriptor paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.format); + WriteParam(aMsg, aParam.width); + WriteParam(aMsg, aParam.height); + WriteParam(aMsg, aParam.stride); + WriteParam(aMsg, aParam.opacity); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + return ReadParam(aMsg, aIter, &aResult->format) && + ReadParam(aMsg, aIter, &aResult->width) && + ReadParam(aMsg, aIter, &aResult->height) && + ReadParam(aMsg, aIter, &aResult->stride) && + ReadParam(aMsg, aIter, &aResult->opacity); + } +}; + +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::BuiltDisplayListDescriptor> + : public PlainOldDataSerializer<mozilla::wr::BuiltDisplayListDescriptor> {}; + +template <> +struct ParamTraits<mozilla::wr::WebRenderError> + : public ContiguousEnumSerializer<mozilla::wr::WebRenderError, + mozilla::wr::WebRenderError::INITIALIZE, + mozilla::wr::WebRenderError::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> {}; + +} // 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..424e6b03d5 --- /dev/null +++ b/gfx/layers/wr/WebRenderScrollData.cpp @@ -0,0 +1,371 @@ +/* -*- 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 "Layers.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), + 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::Initialize( + WebRenderScrollData& aOwner, nsDisplayItem* aItem, int32_t aDescendantCount, + const ActiveScrolledRoot* aStopAtAsr, + const Maybe<gfx::Matrix4x4>& aAncestorTransform) { + MOZ_ASSERT(aDescendantCount >= 0); // Ensure value is valid + MOZ_ASSERT(mDescendantCount == + -1); // Don't allow re-setting an already set value + mDescendantCount = aDescendantCount; + + 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->ReferenceFrame(), Nothing(), nullptr); + 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; + } + + // 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; + } +} + +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]); +} + +CSSTransformMatrix WebRenderLayerScrollData::GetTransformTyped() const { + return ViewAs<CSSTransformMatrix>(GetTransform()); +} + +void WebRenderLayerScrollData::Dump(std::ostream& aOut, + const WebRenderScrollData& aOwner) const { + aOut << "WebRenderLayerScrollData(" << this + << "), descendantCount=" << mDescendantCount; + for (size_t i = 0; i < mScrollIds.Length(); i++) { + aOut << ", metadata" << i << "=" << aOwner.GetScrollMetadata(mScrollIds[i]); + } + if (!mAncestorTransform.IsIdentity()) { + aOut << ", ancestorTransform=" << mAncestorTransform; + } + if (!mTransform.IsIdentity()) { + aOut << ", transform=" << mTransform; + if (mTransformIsPerspective) { + aOut << ", transformIsPerspective"; + } + } + aOut << ", visible=" << mVisibleRegion; + 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), mIsFirstPaint(false), mPaintSequenceNumber(0) {} + +WebRenderScrollData::WebRenderScrollData(WebRenderLayerManager* aManager, + nsDisplayListBuilder* aBuilder) + : mManager(aManager), + mBuilder(aBuilder), + mIsFirstPaint(false), + mPaintSequenceNumber(0) {} + +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( + const WebRenderLayerScrollData& aData) { + mLayerScrollData.AppendElement(aData); + return mLayerScrollData.Length() - 1; +} + +size_t WebRenderScrollData::GetLayerCount() const { + return mLayerScrollData.Length(); +} + +const WebRenderLayerScrollData* WebRenderScrollData::GetLayerData( + size_t aIndex) const { + 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]; +} + +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( + Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mDescendantCount); + WriteParam(aMsg, aParam.mScrollIds); + WriteParam(aMsg, aParam.mAncestorTransform); + WriteParam(aMsg, aParam.mTransform); + WriteParam(aMsg, aParam.mTransformIsPerspective); + WriteParam(aMsg, aParam.mVisibleRegion); + WriteParam(aMsg, aParam.mRemoteDocumentSize); + WriteParam(aMsg, aParam.mReferentId); + WriteParam(aMsg, aParam.mEventRegionsOverride); + WriteParam(aMsg, aParam.mScrollbarData); + WriteParam(aMsg, aParam.mScrollbarAnimationId); + WriteParam(aMsg, aParam.mFixedPositionAnimationId); + WriteParam(aMsg, aParam.mFixedPositionSides); + WriteParam(aMsg, aParam.mFixedPosScrollContainerId); + WriteParam(aMsg, aParam.mStickyPosScrollContainerId); + WriteParam(aMsg, aParam.mStickyScrollRangeOuter); + WriteParam(aMsg, aParam.mStickyScrollRangeInner); + WriteParam(aMsg, aParam.mStickyPositionAnimationId); + WriteParam(aMsg, aParam.mZoomAnimationId); + WriteParam(aMsg, aParam.mAsyncZoomContainerId); +} + +bool ParamTraits<mozilla::layers::WebRenderLayerScrollData>::Read( + const Message* aMsg, PickleIterator* aIter, paramType* aResult) { + return ReadParam(aMsg, aIter, &aResult->mDescendantCount) && + ReadParam(aMsg, aIter, &aResult->mScrollIds) && + ReadParam(aMsg, aIter, &aResult->mAncestorTransform) && + ReadParam(aMsg, aIter, &aResult->mTransform) && + ReadParam(aMsg, aIter, &aResult->mTransformIsPerspective) && + ReadParam(aMsg, aIter, &aResult->mVisibleRegion) && + ReadParam(aMsg, aIter, &aResult->mRemoteDocumentSize) && + ReadParam(aMsg, aIter, &aResult->mReferentId) && + ReadParam(aMsg, aIter, &aResult->mEventRegionsOverride) && + ReadParam(aMsg, aIter, &aResult->mScrollbarData) && + ReadParam(aMsg, aIter, &aResult->mScrollbarAnimationId) && + ReadParam(aMsg, aIter, &aResult->mFixedPositionAnimationId) && + ReadParam(aMsg, aIter, &aResult->mFixedPositionSides) && + ReadParam(aMsg, aIter, &aResult->mFixedPosScrollContainerId) && + ReadParam(aMsg, aIter, &aResult->mStickyPosScrollContainerId) && + ReadParam(aMsg, aIter, &aResult->mStickyScrollRangeOuter) && + ReadParam(aMsg, aIter, &aResult->mStickyScrollRangeInner) && + ReadParam(aMsg, aIter, &aResult->mStickyPositionAnimationId) && + ReadParam(aMsg, aIter, &aResult->mZoomAnimationId) && + ReadParam(aMsg, aIter, &aResult->mAsyncZoomContainerId); +} + +void ParamTraits<mozilla::layers::WebRenderScrollData>::Write( + Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mScrollMetadatas); + WriteParam(aMsg, aParam.mLayerScrollData); + WriteParam(aMsg, aParam.mIsFirstPaint); + WriteParam(aMsg, aParam.mPaintSequenceNumber); +} + +bool ParamTraits<mozilla::layers::WebRenderScrollData>::Read( + const Message* aMsg, PickleIterator* aIter, paramType* aResult) { + return ReadParam(aMsg, aIter, &aResult->mScrollMetadatas) && + ReadParam(aMsg, aIter, &aResult->mLayerScrollData) && + ReadParam(aMsg, aIter, &aResult->mIsFirstPaint) && + ReadParam(aMsg, aIter, &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..b0e4b50e26 --- /dev/null +++ b/gfx/layers/wr/WebRenderScrollData.h @@ -0,0 +1,322 @@ +/* -*- 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/LayerAttributes.h" +#include "mozilla/layers/FocusTarget.h" +#include "mozilla/layers/WebRenderMessageUtils.h" +#include "mozilla/webrender/WebRenderTypes.h" +#include "mozilla/HashTable.h" +#include "mozilla/Maybe.h" +#include "nsTArrayForwardDeclare.h" + +class nsDisplayListBuilder; +class nsDisplayItem; + +namespace mozilla { + +struct ActiveScrolledRoot; + +namespace layers { + +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(); + + void InitializeRoot(int32_t aDescendantCount); + void Initialize(WebRenderScrollData& aOwner, nsDisplayItem* aItem, + int32_t aDescendantCount, + const ActiveScrolledRoot* aStopAtAsr, + const Maybe<gfx::Matrix4x4>& aAncestorTransform); + + 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; } + 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; } + + EventRegions GetEventRegions() const { return EventRegions(); } + void SetEventRegionsOverride(const EventRegionsOverride& aOverride) { + mEventRegionsOverride = aOverride; + } + EventRegionsOverride GetEventRegionsOverride() const { + return mEventRegionsOverride; + } + + void SetVisibleRegion(const LayerIntRegion& aRegion) { + mVisibleRegion = aRegion; + } + const LayerIntRegion& GetVisibleRegion() const { return mVisibleRegion; } + 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(ScrollableLayerGuid::ViewID aId) { + mFixedPosScrollContainerId = aId; + } + ScrollableLayerGuid::ViewID GetFixedPositionScrollContainerId() const { + return mFixedPosScrollContainerId; + } + + void SetStickyPositionScrollContainerId(ScrollableLayerGuid::ViewID aId) { + mStickyPosScrollContainerId = aId; + } + ScrollableLayerGuid::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 ScrollableLayerGuid::ViewID aId) { + mAsyncZoomContainerId = Some(aId); + } + Maybe<ScrollableLayerGuid::ViewID> GetAsyncZoomContainerId() const { + return mAsyncZoomContainerId; + } + bool IsAsyncZoomContainer() const { return mAsyncZoomContainerId.isSome(); } + + void Dump(std::ostream& aOut, const WebRenderScrollData& aOwner) const; + + friend struct IPC::ParamTraits<WebRenderLayerScrollData>; + + 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; + gfx::Matrix4x4 mTransform; + bool mTransformIsPerspective; + LayerIntRegion mVisibleRegion; + // 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; + ScrollableLayerGuid::ViewID mFixedPosScrollContainerId; + ScrollableLayerGuid::ViewID mStickyPosScrollContainerId; + LayerRectAbsolute mStickyScrollRangeOuter; + LayerRectAbsolute mStickyScrollRangeInner; + Maybe<uint64_t> mStickyPositionAnimationId; + Maybe<uint64_t> mZoomAnimationId; + Maybe<ScrollableLayerGuid::ViewID> mAsyncZoomContainerId; +}; + +// 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 final { + public: + WebRenderScrollData(); + explicit WebRenderScrollData(WebRenderLayerManager* aManager, + nsDisplayListBuilder* aBuilder); + + 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(const 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; + + 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: + // 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(Message* aMsg, const paramType& aParam); + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult); +}; + +template <> +struct ParamTraits<mozilla::layers::WebRenderScrollData> { + typedef mozilla::layers::WebRenderScrollData paramType; + + static void Write(Message* aMsg, const paramType& aParam); + + static bool Read(const Message* aMsg, PickleIterator* aIter, + 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..1f681a7cff --- /dev/null +++ b/gfx/layers/wr/WebRenderScrollDataWrapper.h @@ -0,0 +1,424 @@ +/* -*- 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/CompositorBridgeParent.h" +#include "mozilla/layers/WebRenderBridgeParent.h" +#include "mozilla/layers/WebRenderScrollData.h" + +namespace mozilla { +namespace layers { + +/* + * This class is a wrapper to walk through a WebRenderScrollData object, with + * an exposed API that is template-compatible to LayerMetricsWrapper. This + * allows APZ to walk through both layer trees and WebRender scroll metadata + * structures without a lot of code duplication. (Note that not all functions + * from LayerMetricsWrapper are implemented here, only the ones we've needed in + * APZ code so far.) + * + * A WebRenderScrollData object is basically a flattened layer tree, with a + * number of WebRenderLayerScrollData objects that have a 1:1 correspondence + * to layers in a layer tree. Therefore the mLayer pointer in this class can + * be considered equivalent to the mLayer pointer in the LayerMetricsWrapper. + * There are some extra fields (mData, mLayerIndex, mContainingSubtreeLastIndex) + * to move around between these "layers" given the flattened representation. + * The mMetadataIndex field in this class corresponds to the mIndex field in + * LayerMetricsWrapper, as both classes also need to manage walking through + * "virtual" container layers implied by the list of ScrollMetadata objects. + * + * 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. + * + * Refer to LayerMetricsWrapper.h for actual documentation on the exposed API. + */ +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(); + 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 the "topmost" layer, and the transform is + // associated with the "bottommost" layer. If there is only one + // scrollmetadata on the layer, then it is both "topmost" and "bottommost" + // and we combine the two transforms. + + gfx::Matrix4x4 transform; + if (AtTopLayer()) { + transform = mLayer->GetAncestorTransform(); + } + if (AtBottomLayer()) { + transform = transform * mLayer->GetTransform(); + } + return transform; + } + + CSSTransformMatrix GetTransformTyped() const { + return ViewAs<CSSTransformMatrix>(GetTransform()); + } + + bool TransformIsPerspective() const { + MOZ_ASSERT(IsValid()); + + if (AtBottomLayer()) { + return mLayer->GetTransformIsPerspective(); + } + return false; + } + + EventRegions GetEventRegions() const { + MOZ_ASSERT(IsValid()); + + if (AtBottomLayer()) { + return mLayer->GetEventRegions(); + } + return EventRegions(); + } + + LayerIntRegion GetVisibleRegion() const { + MOZ_ASSERT(IsValid()); + + if (AtBottomLayer()) { + return mLayer->GetVisibleRegion(); + } + + return ViewAs<LayerPixel>( + TransformBy(mLayer->GetTransformTyped(), mLayer->GetVisibleRegion()), + 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(); + } + + Maybe<ParentLayerIntRect> GetClipRect() const { + // TODO + 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(); + } + + bool IsBackfaceHidden() const { + // This is only used by APZCTM hit testing, and WR does its own + // hit testing, so no need to implement this. + return false; + } + + bool IsAsyncZoomContainer() const { + MOZ_ASSERT(IsValid()); + return mLayer->IsAsyncZoomContainer(); + } + + const void* GetLayer() const { + MOZ_ASSERT(IsValid()); + return mLayer; + } + + 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..6e9a6d88ab --- /dev/null +++ b/gfx/layers/wr/WebRenderTextureHost.cpp @@ -0,0 +1,253 @@ +/* -*- 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( + const SurfaceDescriptor& aDesc, TextureFlags aFlags, TextureHost* aTexture, + wr::ExternalImageId& aExternalImageId) + : TextureHost(aFlags), mWrappedTextureHost(aTexture) { + // 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_ASSERT(mWrappedTextureHost); + + 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(); } + +bool WebRenderTextureHost::Lock() { + MOZ_ASSERT(mWrappedTextureHost->AsBufferTextureHost()); + + if (mWrappedTextureHost->AsBufferTextureHost()) { + return mWrappedTextureHost->Lock(); + } + return false; +} + +void WebRenderTextureHost::Unlock() { + MOZ_ASSERT(mWrappedTextureHost->AsBufferTextureHost()); + + if (mWrappedTextureHost->AsBufferTextureHost()) { + mWrappedTextureHost->Unlock(); + } +} + +void WebRenderTextureHost::PrepareTextureSource( + CompositableTextureSourceRef& aTexture) { + MOZ_ASSERT(mWrappedTextureHost->AsBufferTextureHost()); + + if (mWrappedTextureHost->AsBufferTextureHost()) { + mWrappedTextureHost->PrepareTextureSource(aTexture); + } +} + +bool WebRenderTextureHost::BindTextureSource( + CompositableTextureSourceRef& aTexture) { + MOZ_ASSERT(mWrappedTextureHost->AsBufferTextureHost()); + + if (mWrappedTextureHost->AsBufferTextureHost()) { + return mWrappedTextureHost->BindTextureSource(aTexture); + } + return false; +} + +void WebRenderTextureHost::UnbindTextureSource() { + if (mWrappedTextureHost->AsBufferTextureHost()) { + mWrappedTextureHost->UnbindTextureSource(); + } + // Handle read unlock + TextureHost::UnbindTextureSource(); +} + +void WebRenderTextureHost::SetTextureSourceProvider( + TextureSourceProvider* aProvider) { + // During using WebRender, only BasicCompositor could exist + MOZ_ASSERT(!aProvider || aProvider->AsBasicCompositor()); + MOZ_ASSERT(mWrappedTextureHost->AsBufferTextureHost()); + + if (mWrappedTextureHost->AsBufferTextureHost()) { + mWrappedTextureHost->SetTextureSourceProvider(aProvider); + } +} + +already_AddRefed<gfx::DataSourceSurface> WebRenderTextureHost::GetAsSurface() { + return mWrappedTextureHost->GetAsSurface(); +} + +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::NotifyNotUsed() { +#ifdef MOZ_WIDGET_ANDROID + if (mWrappedTextureHost->AsSurfaceTextureHost()) { + wr::RenderThread::Get()->NotifyNotUsed(wr::AsUint64(GetExternalImageKey())); + } +#endif + TextureHost::NotifyNotUsed(); +} + +void WebRenderTextureHost::MaybeNotifyForUse(wr::TransactionBuilder& aTxn) { +#if defined(MOZ_WIDGET_ANDROID) + if (mWrappedTextureHost->AsSurfaceTextureHost()) { + wr::RenderThread::Get()->NotifyForUse(wr::AsUint64(GetExternalImageKey())); + aTxn.Notify(wr::Checkpoint::FrameTexturesUpdated, + MakeUnique<ScheduleHandleRenderTextureOps>()); + } +#endif +} + +void WebRenderTextureHost::PrepareForUse() { + if (mWrappedTextureHost->AsSurfaceTextureHost() || + mWrappedTextureHost->AsBufferTextureHost()) { + // Call PrepareForUse on render thread. + // See RenderAndroidSurfaceTextureHostOGL::PrepareForUse. + wr::RenderThread::Get()->PrepareForUse(wr::AsUint64(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::HasIntermediateBuffer() const { + return mWrappedTextureHost->HasIntermediateBuffer(); +} + +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() { + return mWrappedTextureHost->SupportsExternalCompositing(); +} + +bool WebRenderTextureHost::NeedsYFlip() const { + bool yFlip = TextureHost::NeedsYFlip(); + if (mWrappedTextureHost->AsSurfaceTextureHost()) { + MOZ_ASSERT(yFlip); + // With WebRender, SurfaceTextureHost always requests y-flip. + // But y-flip should not be handled, since + // SurfaceTexture.getTransformMatrix() is not handled yet. + // See Bug 1507076. + yFlip = false; + } + return yFlip; +} + +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(); +} + +} // namespace mozilla::layers diff --git a/gfx/layers/wr/WebRenderTextureHost.h b/gfx/layers/wr/WebRenderTextureHost.h new file mode 100644 index 0000000000..e4e930e9bc --- /dev/null +++ b/gfx/layers/wr/WebRenderTextureHost.h @@ -0,0 +1,111 @@ +/* -*- 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(const SurfaceDescriptor& aDesc, TextureFlags aFlags, + TextureHost* aTexture, + wr::ExternalImageId& aExternalImageId); + virtual ~WebRenderTextureHost(); + + void DeallocateDeviceData() override {} + + bool Lock() override; + + void Unlock() override; + + void PrepareTextureSource(CompositableTextureSourceRef& aTexture) override; + bool BindTextureSource(CompositableTextureSourceRef& aTexture) override; + void UnbindTextureSource() override; + void SetTextureSourceProvider(TextureSourceProvider* aProvider) 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::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 + + WebRenderTextureHost* AsWebRenderTextureHost() override { return this; } + + virtual void PrepareForUse() override; + + wr::ExternalImageId GetExternalImageKey(); + + int32_t GetRGBStride(); + + bool HasIntermediateBuffer() const override; + + 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() override; + + bool NeedsYFlip() const 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); + + protected: + 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..b9f885d6b6 --- /dev/null +++ b/gfx/layers/wr/WebRenderUserData.cpp @@ -0,0 +1,432 @@ +/* -*- 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 "BasicLayers.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" + +namespace mozilla { +namespace layers { + +void WebRenderBackgroundData::AddWebRenderCommands( + wr::DisplayListBuilder& aBuilder) { + aBuilder.PushRect(mBounds, mBounds, true, 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, ContainerProducerID aProducerId) { + MOZ_ASSERT(aFrame); + + if (!aFrame->HasProperty(WebRenderUserDataProperty::Key())) { + 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<WebRenderImageData> image = + GetWebRenderUserData<WebRenderImageData>(aFrame, type); + if (image && image->UsingSharedSurface(aProducerId)) { + 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->RemoveEntry(this); } + +WebRenderBridgeChild* WebRenderUserData::WrBridge() const { + return mManager->WrBridge(); +} + +WebRenderImageData::WebRenderImageData(RenderRootStateManager* aManager, + nsDisplayItem* aItem) + : WebRenderUserData(aManager, aItem), mOwnsKey(false) {} + +WebRenderImageData::WebRenderImageData(RenderRootStateManager* aManager, + uint32_t aDisplayItemKey, + nsIFrame* aFrame) + : WebRenderUserData(aManager, aDisplayItemKey, aFrame), mOwnsKey(false) {} + +WebRenderImageData::~WebRenderImageData() { + ClearImageKey(); + + if (mPipelineId) { + mManager->RemovePipelineIdForCompositable(mPipelineId.ref()); + } +} + +bool WebRenderImageData::UsingSharedSurface( + ContainerProducerID aProducerId) const { + if (!mContainer || !mKey || mOwnsKey) { + return false; + } + + // If this is just an update with the same image key, then we know that the + // share request initiated an asynchronous update so that we don't need to + // rebuild the scene. + wr::ImageKey key; + nsresult rv = SharedSurfacesChild::Share( + mContainer, mManager, mManager->AsyncResourceUpdates(), key, aProducerId); + return NS_SUCCEEDED(rv) && mKey.ref() == key; +} + +void WebRenderImageData::ClearImageKey() { + if (mKey) { + // If we don't own the key, then the owner is responsible for discarding the + // key when appropriate. + if (mOwnsKey) { + mManager->AddImageKeyForDiscard(mKey.value()); + if (mTextureOfImage) { + WrBridge()->ReleaseTextureOfImage(mKey.value()); + mTextureOfImage = nullptr; + } + } + mKey.reset(); + } + mOwnsKey = false; + MOZ_ASSERT(!mTextureOfImage); +} + +Maybe<wr::ImageKey> WebRenderImageData::UpdateImageKey( + ImageContainer* aContainer, wr::IpcResourceUpdateQueue& aResources, + bool aFallback) { + MOZ_ASSERT(aContainer); + + if (mContainer != aContainer) { + mContainer = aContainer; + } + + wr::WrImageKey key; + if (!aFallback) { + nsresult rv = SharedSurfacesChild::Share(aContainer, mManager, aResources, + key, kContainerProducerID_Invalid); + if (NS_SUCCEEDED(rv)) { + // Ensure that any previously owned keys are released before replacing. We + // don't own this key, the surface itself owns it, so that it can be + // shared across multiple elements. + ClearImageKey(); + mKey = Some(key); + return mKey; + } + + if (rv != NS_ERROR_NOT_IMPLEMENTED) { + // We should be using the shared surface but somehow sharing it failed. + ClearImageKey(); + return Nothing(); + } + } + + CreateImageClientIfNeeded(); + if (!mImageClient) { + return Nothing(); + } + + MOZ_ASSERT(mImageClient->AsImageClientSingle()); + + ImageClientSingle* imageClient = mImageClient->AsImageClientSingle(); + uint32_t oldCounter = imageClient->GetLastUpdateGenerationCounter(); + + bool ret = imageClient->UpdateImage(aContainer, /* unused */ 0); + 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(); + key = WrBridge()->GetNextImageKey(); + aResources.PushExternalImageForTexture(extId.ref(), key, currentTexture, + /* aIsUpdate */ false); + mKey = Some(key); + } + + mTextureOfImage = currentTexture; + mOwnsKey = true; + + 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, VideoInfo::Rotation 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()->AddPipelineIdForAsyncCompositable( + mPipelineId.ref(), aContainer->GetAsyncContainerHandle()); + 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. + wr::LayoutRect r = wr::ToLayoutRect(aBounds); + aBuilder.PushIFrame(r, 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(); + } +} + +WebRenderFallbackData::WebRenderFallbackData(RenderRootStateManager* aManager, + nsDisplayItem* aItem) + : WebRenderUserData(aManager, aItem), 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(); +} + +void WebRenderCanvasData::SetImageContainer(ImageContainer* aImageContainer) { + mContainer = aImageContainer; +} + +ImageContainer* WebRenderCanvasData::GetImageContainer() { + if (!mContainer) { + mContainer = LayerManager::CreateImageContainer(); + } + return mContainer; +} + +void WebRenderCanvasData::ClearImageContainer() { mContainer = nullptr; } + +WebRenderLocalCanvasData::WebRenderLocalCanvasData( + RenderRootStateManager* aManager, nsDisplayItem* aItem) + : WebRenderUserData(aManager, aItem) {} + +WebRenderLocalCanvasData::~WebRenderLocalCanvasData() = default; + +void WebRenderLocalCanvasData::RequestFrameReadback() { + if (mGpuBridge) { + mGpuBridge->SwapChainPresent(mExternalImageId, mGpuTextureId); + } +} + +void WebRenderLocalCanvasData::RefreshExternalImage() { + if (!mDirty) { + return; + } + + const ImageIntRect dirtyRect(0, 0, mDescriptor.width, mDescriptor.height); + // Update the WR external image, forcing the composition of a new frame. + mManager->AsyncResourceUpdates().UpdatePrivateExternalImage( + mExternalImageId, mImageKey, mDescriptor, dirtyRect); + mDirty = false; +} + +WebRenderRemoteData::WebRenderRemoteData(RenderRootStateManager* aManager, + nsDisplayItem* aItem) + : WebRenderUserData(aManager, aItem) {} + +WebRenderRemoteData::~WebRenderRemoteData() { + if (mRemoteBrowser) { + mRemoteBrowser->UpdateEffects(mozilla::dom::EffectsInfo::FullyHidden()); + } +} + +void DestroyWebRenderUserDataTable(WebRenderUserDataTable* aTable) { + for (auto iter = aTable->Iter(); !iter.Done(); iter.Next()) { + iter.UserData()->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..66a74b2a09 --- /dev/null +++ b/gfx/layers/wr/WebRenderUserData.h @@ -0,0 +1,352 @@ +/* -*- 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/webrender/WebRenderAPI.h" +#include "mozilla/layers/AnimationInfo.h" +#include "mozilla/dom/RemoteBrowser.h" +#include "mozilla/UniquePtr.h" +#include "nsIFrame.h" +#include "nsRefPtrHashtable.h" +#include "ImageTypes.h" + +class nsDisplayItemGeometry; + +namespace mozilla { +namespace webgpu { +class WebGPUChild; +} + +namespace wr { +class IpcResourceUpdateQueue; +} + +namespace gfx { +class SourceSurface; +} + +namespace layers { +class BasicLayerManager; +class CanvasLayer; +class ImageClient; +class ImageContainer; +class WebRenderBridgeChild; +class WebRenderCanvasData; +class WebRenderCanvasRenderer; +class WebRenderCanvasRendererAsync; +class WebRenderImageData; +class WebRenderFallbackData; +class WebRenderLocalCanvasData; +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 nsTHashtable<nsRefPtrHashKey<WebRenderUserData>> + WebRenderUserDataRefTable; + + static bool SupportsAsyncUpdate(nsIFrame* aFrame); + + static bool ProcessInvalidateForImage(nsIFrame* aFrame, DisplayItemType aType, + ContainerProducerID aProducerId); + + NS_INLINE_DECL_REFCOUNTING(WebRenderUserData) + + WebRenderUserData(RenderRootStateManager* aManager, nsDisplayItem* aItem); + WebRenderUserData(RenderRootStateManager* aManager, uint32_t mDisplayItemKey, + nsIFrame* aFrame); + + virtual WebRenderImageData* AsImageData() { return nullptr; } + virtual WebRenderFallbackData* AsFallbackData() { return nullptr; } + virtual WebRenderCanvasData* AsCanvasData() { return nullptr; } + virtual WebRenderLocalCanvasData* AsLocalCanvasData() { return nullptr; } + virtual WebRenderGroupData* AsGroupData() { return nullptr; } + + enum class UserDataType { + eImage, + eFallback, + eAPZAnimation, + eAnimation, + eCanvas, + eLocalCanvas, + eRemote, + eGroup, + eMask, + }; + + 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, VideoInfo::Rotation aRotation, + const wr::ImageRendering& aFilter, const wr::MixBlendMode& aMixBlendMode, + bool aIsBackfaceVisible); + + void CreateImageClientIfNeeded(); + + bool IsAsync() { return mPipelineId.isSome(); } + + bool UsingSharedSurface(ContainerProducerID aProducerId) const; + + void ClearImageKey(); + + protected: + Maybe<wr::ImageKey> mKey; + RefPtr<TextureClient> mTextureOfImage; + RefPtr<ImageClient> mImageClient; + Maybe<wr::PipelineId> mPipelineId; + RefPtr<ImageContainer> mContainer; + // The key can be owned by a shared surface that is used by several elements. + // when this is the case the shared surface is responsible for managing the + // destruction of the key. + // TODO: we surely can come up with a simpler/safer way to model this. + bool mOwnsKey; +}; + +/// 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; } + + 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(); + + std::vector<RefPtr<gfx::SourceSurface>> mExternalSurfaces; + RefPtr<BasicLayerManager> mBasicLayerManager; + UniquePtr<nsDisplayItemGeometry> mGeometry; + nsRect mBounds; + nsRect mBuildingRect; + gfx::Size mScale; + + 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(); + + void SetImageContainer(ImageContainer* aImageContainer); + ImageContainer* GetImageContainer(); + void ClearImageContainer(); + + protected: + RefPtr<WebRenderCanvasRendererAsync> mCanvasRenderer; + RefPtr<ImageContainer> mContainer; +}; + +// WebRender data assocatiated with canvases that don't need to +// synchronize across content-GPU process barrier. +class WebRenderLocalCanvasData : public WebRenderUserData { + public: + WebRenderLocalCanvasData(RenderRootStateManager* aManager, + nsDisplayItem* aItem); + virtual ~WebRenderLocalCanvasData(); + + WebRenderLocalCanvasData* AsLocalCanvasData() override { return this; } + UserDataType GetType() override { return UserDataType::eLocalCanvas; } + static UserDataType Type() { return UserDataType::eLocalCanvas; } + + void RequestFrameReadback(); + void RefreshExternalImage(); + + // TODO: introduce a CanvasRenderer derivative to store here? + + WeakPtr<webgpu::WebGPUChild> mGpuBridge; + uint64_t mGpuTextureId = 0; + wr::ExternalImageId mExternalImageId = {0}; + wr::ImageKey mImageKey = {}; + wr::ImageDescriptor mDescriptor; + gfx::SurfaceFormat mFormat = gfx::SurfaceFormat::UNKNOWN; + bool mDirty = false; +}; + +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; +}; + +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 */ |