/* -*- 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 "SharedSurfacesChild.h" #include "SharedSurfacesParent.h" #include "CompositorManagerChild.h" #include "mozilla/gfx/gfxVars.h" #include "mozilla/image/RecyclingSourceSurface.h" #include "mozilla/layers/IpcResourceUpdateQueue.h" #include "mozilla/layers/SourceSurfaceSharedData.h" #include "mozilla/layers/WebRenderBridgeChild.h" #include "mozilla/layers/RenderRootStateManager.h" #include "mozilla/SystemGroup.h" // for SystemGroup namespace mozilla { namespace layers { using namespace mozilla::gfx; /* static */ UserDataKey SharedSurfacesChild::sSharedKey; SharedSurfacesChild::ImageKeyData::ImageKeyData( RenderRootStateManager* aManager, const wr::ImageKey& aImageKey) : mManager(aManager), mImageKey(aImageKey) {} SharedSurfacesChild::ImageKeyData::ImageKeyData( SharedSurfacesChild::ImageKeyData&& aOther) : mManager(std::move(aOther.mManager)), mDirtyRect(std::move(aOther.mDirtyRect)), mImageKey(aOther.mImageKey) {} SharedSurfacesChild::ImageKeyData& SharedSurfacesChild::ImageKeyData::operator=( SharedSurfacesChild::ImageKeyData&& aOther) { mManager = std::move(aOther.mManager); mDirtyRect = std::move(aOther.mDirtyRect); mImageKey = aOther.mImageKey; return *this; } SharedSurfacesChild::ImageKeyData::~ImageKeyData() {} void SharedSurfacesChild::ImageKeyData::MergeDirtyRect( const Maybe& aDirtyRect) { if (mDirtyRect) { if (aDirtyRect) { mDirtyRect->UnionRect(mDirtyRect.ref(), aDirtyRect.ref()); } } else { mDirtyRect = aDirtyRect; } } SharedSurfacesChild::SharedUserData::~SharedUserData() { if (mShared || !mKeys.IsEmpty()) { if (NS_IsMainThread()) { SharedSurfacesChild::Unshare(mId, mShared, mKeys); } else { class DestroyRunnable final : public Runnable { public: DestroyRunnable(const wr::ExternalImageId& aId, bool aReleaseId, nsTArray&& aKeys) : Runnable("SharedSurfacesChild::SharedUserData::DestroyRunnable"), mId(aId), mReleaseId(aReleaseId), mKeys(std::move(aKeys)) {} NS_IMETHOD Run() override { SharedSurfacesChild::Unshare(mId, mReleaseId, mKeys); return NS_OK; } private: wr::ExternalImageId mId; bool mReleaseId; AutoTArray mKeys; }; nsCOMPtr task = new DestroyRunnable(mId, mShared, std::move(mKeys)); SystemGroup::Dispatch(TaskCategory::Other, task.forget()); } } } wr::ImageKey SharedSurfacesChild::SharedUserData::UpdateKey( RenderRootStateManager* aManager, wr::IpcResourceUpdateQueue& aResources, const Maybe& aDirtyRect) { MOZ_ASSERT(aManager); MOZ_ASSERT(!aManager->IsDestroyed()); // We iterate through all of the items to ensure we clean up the old // RenderRootStateManager references. Most of the time there will be few // entries and this should not be particularly expensive compared to the // cost of duplicating image keys. In an ideal world, we would generate a // single key for the surface, and it would be usable on all of the // renderer instances. For now, we must allocate a key for each WR bridge. wr::ImageKey key; bool found = false; auto i = mKeys.Length(); while (i > 0) { --i; ImageKeyData& entry = mKeys[i]; if (entry.mManager->IsDestroyed()) { mKeys.RemoveElementAt(i); } else if (entry.mManager == aManager) { WebRenderBridgeChild* wrBridge = aManager->WrBridge(); MOZ_ASSERT(wrBridge); // Even if the manager is the same, its underlying WebRenderBridgeChild // can change state. If our namespace differs, then our old key has // already been discarded. bool ownsKey = wrBridge->GetNamespace() == entry.mImageKey.mNamespace; if (!ownsKey) { entry.mImageKey = wrBridge->GetNextImageKey(); entry.TakeDirtyRect(); aResources.AddExternalImage(mId, entry.mImageKey); } else { entry.MergeDirtyRect(aDirtyRect); Maybe dirtyRect = entry.TakeDirtyRect(); if (dirtyRect) { MOZ_ASSERT(mShared); aResources.UpdateExternalImage(mId, entry.mImageKey, ViewAs(dirtyRect.ref())); } } key = entry.mImageKey; found = true; } else { // We don't have the resource update queue for this manager, so just // accumulate the dirty rects until it is requested. entry.MergeDirtyRect(aDirtyRect); } } if (!found) { key = aManager->WrBridge()->GetNextImageKey(); ImageKeyData data(aManager, key); mKeys.AppendElement(std::move(data)); aResources.AddExternalImage(mId, key); } return key; } /* static */ SourceSurfaceSharedData* SharedSurfacesChild::AsSourceSurfaceSharedData( SourceSurface* aSurface) { MOZ_ASSERT(aSurface); switch (aSurface->GetType()) { case SurfaceType::DATA_SHARED: return static_cast(aSurface); case SurfaceType::DATA_RECYCLING_SHARED: { auto recycleSurface = static_cast(aSurface); auto childSurface = recycleSurface->GetChildSurface(); return static_cast(childSurface); } default: return nullptr; } } /* static */ void SharedSurfacesChild::DestroySharedUserData(void* aClosure) { MOZ_ASSERT(aClosure); auto data = static_cast(aClosure); delete data; } /* static */ nsresult SharedSurfacesChild::ShareInternal(SourceSurfaceSharedData* aSurface, SharedUserData** aUserData) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aSurface); MOZ_ASSERT(aUserData); CompositorManagerChild* manager = CompositorManagerChild::GetInstance(); if (NS_WARN_IF(!manager || !manager->CanSend() || !gfxVars::UseWebRender())) { // We cannot try to share the surface, most likely because the GPU process // crashed. Ideally, we would retry when it is ready, but the handles may be // a scarce resource, which can cause much more serious problems if we run // out. Better to copy into a fresh buffer later. aSurface->FinishedSharing(); return NS_ERROR_NOT_INITIALIZED; } SharedUserData* data = static_cast(aSurface->GetUserData(&sSharedKey)); if (!data) { data = new SharedUserData(manager->GetNextExternalImageId()); aSurface->AddUserData(&sSharedKey, data, DestroySharedUserData); } else if (!manager->OwnsExternalImageId(data->Id())) { // If the id isn't owned by us, that means the bridge was reinitialized, due // to the GPU process crashing. All previous mappings have been released. data->SetId(manager->GetNextExternalImageId()); } else if (data->IsShared()) { // It has already been shared with the GPU process. *aUserData = data; return NS_OK; } // Ensure that the handle doesn't get released until after we have finished // sending the buffer to the GPU process and/or reallocating it. // FinishedSharing is not a sufficient condition because another thread may // decide we are done while we are in the processing of sharing our newly // reallocated handle. Once it goes out of scope, it may release the handle. SourceSurfaceSharedData::HandleLock lock(aSurface); // If we live in the same process, then it is a simple matter of directly // asking the parent instance to store a pointer to the same data, no need // to map the data into our memory space twice. auto pid = manager->OtherPid(); if (pid == base::GetCurrentProcId()) { SharedSurfacesParent::AddSameProcess(data->Id(), aSurface); data->MarkShared(); *aUserData = data; return NS_OK; } // Attempt to share a handle with the GPU process. The handle may or may not // be available -- it will only be available if it is either not yet finalized // and/or if it has been finalized but never used for drawing in process. ipc::SharedMemoryBasic::Handle handle = ipc::SharedMemoryBasic::NULLHandle(); nsresult rv = aSurface->ShareToProcess(pid, handle); if (rv == NS_ERROR_NOT_AVAILABLE) { // It is at least as expensive to copy the image to the GPU process if we // have already closed the handle necessary to share, but if we reallocate // the shared buffer to get a new handle, we can save some memory. if (NS_WARN_IF(!aSurface->ReallocHandle())) { return NS_ERROR_OUT_OF_MEMORY; } // Reattempt the sharing of the handle to the GPU process. rv = aSurface->ShareToProcess(pid, handle); } if (NS_WARN_IF(NS_FAILED(rv))) { MOZ_ASSERT(rv != NS_ERROR_NOT_AVAILABLE); return rv; } SurfaceFormat format = aSurface->GetFormat(); MOZ_RELEASE_ASSERT( format == SurfaceFormat::B8G8R8X8 || format == SurfaceFormat::B8G8R8A8, "bad format"); data->MarkShared(); manager->SendAddSharedSurface( data->Id(), SurfaceDescriptorShared(aSurface->GetSize(), aSurface->Stride(), format, handle)); *aUserData = data; return NS_OK; } /* static */ void SharedSurfacesChild::Share(SourceSurfaceSharedData* aSurface) { MOZ_ASSERT(aSurface); // The IPDL actor to do sharing can only be accessed on the main thread so we // need to dispatch if off the main thread. However there is no real danger if // we end up racing because if it is already shared, this method will do // nothing. if (!NS_IsMainThread()) { class ShareRunnable final : public Runnable { public: explicit ShareRunnable(SourceSurfaceSharedData* aSurface) : Runnable("SharedSurfacesChild::Share"), mSurface(aSurface) {} NS_IMETHOD Run() override { SharedUserData* unused = nullptr; SharedSurfacesChild::ShareInternal(mSurface, &unused); return NS_OK; } private: RefPtr mSurface; }; SystemGroup::Dispatch(TaskCategory::Other, MakeAndAddRef(aSurface)); return; } SharedUserData* unused = nullptr; SharedSurfacesChild::ShareInternal(aSurface, &unused); } /* static */ nsresult SharedSurfacesChild::Share(SourceSurfaceSharedData* aSurface, RenderRootStateManager* aManager, wr::IpcResourceUpdateQueue& aResources, wr::ImageKey& aKey) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aSurface); MOZ_ASSERT(aManager); // Each time the surface changes, the producers of SourceSurfaceSharedData // surfaces promise to increment the invalidation counter each time the // surface has changed. We can use this counter to determine whether or not // we should update our paired ImageKey. Maybe dirtyRect = aSurface->TakeDirtyRect(); SharedUserData* data = nullptr; nsresult rv = SharedSurfacesChild::ShareInternal(aSurface, &data); if (NS_SUCCEEDED(rv)) { MOZ_ASSERT(data); aKey = data->UpdateKey(aManager, aResources, dirtyRect); } return rv; } /* static */ nsresult SharedSurfacesChild::Share(SourceSurface* aSurface, RenderRootStateManager* aManager, wr::IpcResourceUpdateQueue& aResources, wr::ImageKey& aKey) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aSurface); MOZ_ASSERT(aManager); auto sharedSurface = AsSourceSurfaceSharedData(aSurface); if (!sharedSurface) { return NS_ERROR_NOT_IMPLEMENTED; } return Share(sharedSurface, aManager, aResources, aKey); } /* static */ nsresult SharedSurfacesChild::Share(ImageContainer* aContainer, RenderRootStateManager* aManager, wr::IpcResourceUpdateQueue& aResources, wr::ImageKey& aKey, ContainerProducerID aProducerId) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aContainer); MOZ_ASSERT(aManager); if (aContainer->IsAsync()) { return NS_ERROR_NOT_IMPLEMENTED; } AutoTArray images; aContainer->GetCurrentImages(&images); if (images.IsEmpty()) { return NS_ERROR_NOT_AVAILABLE; } if (aProducerId != kContainerProducerID_Invalid && images[0].mProducerID != aProducerId) { // If the producer ID of the surface in the container does not match the // expected producer ID, then we do not want to proceed with sharing. This // is useful for when callers are unsure if given container is for the same // producer / underlying image request. return NS_ERROR_FAILURE; } RefPtr surface = images[0].mImage->GetAsSourceSurface(); if (!surface) { return NS_ERROR_NOT_IMPLEMENTED; } auto sharedSurface = AsSourceSurfaceSharedData(surface); if (!sharedSurface) { return NS_ERROR_NOT_IMPLEMENTED; } SharedSurfacesAnimation* anim = aContainer->GetSharedSurfacesAnimation(); if (anim) { return anim->UpdateKey(surface, sharedSurface, aManager, aResources, aKey); } return Share(sharedSurface, aManager, aResources, aKey); } /* static */ nsresult SharedSurfacesChild::Share(SourceSurface* aSurface, wr::ExternalImageId& aId) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aSurface); auto sharedSurface = AsSourceSurfaceSharedData(aSurface); if (!sharedSurface) { return NS_ERROR_NOT_IMPLEMENTED; } // The external image ID does not change with the invalidation counter. The // caller of this should be aware of the invalidations of the surface through // another mechanism (e.g. imgRequestProxy listener notifications). SharedUserData* data = nullptr; nsresult rv = ShareInternal(sharedSurface, &data); if (NS_SUCCEEDED(rv)) { MOZ_ASSERT(data); aId = data->Id(); } return rv; } /* static */ void SharedSurfacesChild::Unshare(const wr::ExternalImageId& aId, bool aReleaseId, nsTArray& aKeys) { MOZ_ASSERT(NS_IsMainThread()); for (const auto& entry : aKeys) { if (!entry.mManager->IsDestroyed()) { entry.mManager->AddImageKeyForDiscard(entry.mImageKey); } } if (!aReleaseId) { // We don't own the external image ID itself. return; } CompositorManagerChild* manager = CompositorManagerChild::GetInstance(); if (MOZ_UNLIKELY(!manager || !manager->CanSend())) { return; } if (manager->OtherPid() == base::GetCurrentProcId()) { // We are in the combined UI/GPU process. Call directly to it to remove its // wrapper surface to free the underlying buffer, but only if the external // image ID is owned by the manager. It can be different if the surface was // last shared with the GPU process, which crashed several times, and its // job was moved into the parent process. if (manager->OwnsExternalImageId(aId)) { SharedSurfacesParent::RemoveSameProcess(aId); } } else if (manager->OwnsExternalImageId(aId)) { // Only attempt to release current mappings in the GPU process. It is // possible we had a surface that was previously shared, the GPU process // crashed / was restarted, and then we freed the surface. In that case // we know the mapping has already been freed. manager->SendRemoveSharedSurface(aId); } } /* static */ Maybe SharedSurfacesChild::GetExternalId( const SourceSurfaceSharedData* aSurface) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aSurface); SharedUserData* data = static_cast(aSurface->GetUserData(&sSharedKey)); if (!data || !data->IsShared()) { return Nothing(); } return Some(data->Id()); } /* static */ nsresult SharedSurfacesChild::UpdateAnimation(ImageContainer* aContainer, SourceSurface* aSurface, const IntRect& aDirtyRect) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aContainer); MOZ_ASSERT(!aContainer->IsAsync()); MOZ_ASSERT(aSurface); // If we aren't using shared surfaces, then is nothing to do. auto sharedSurface = SharedSurfacesChild::AsSourceSurfaceSharedData(aSurface); if (!sharedSurface) { MOZ_ASSERT(!aContainer->GetSharedSurfacesAnimation()); return NS_ERROR_NOT_IMPLEMENTED; } SharedSurfacesAnimation* anim = aContainer->EnsureSharedSurfacesAnimation(); MOZ_ASSERT(anim); return anim->SetCurrentFrame(aSurface, sharedSurface, aDirtyRect); } AnimationImageKeyData::AnimationImageKeyData(RenderRootStateManager* aManager, const wr::ImageKey& aImageKey) : SharedSurfacesChild::ImageKeyData(aManager, aImageKey), mRecycling(false) {} AnimationImageKeyData::AnimationImageKeyData(AnimationImageKeyData&& aOther) : SharedSurfacesChild::ImageKeyData(std::move(aOther)), mPendingRelease(std::move(aOther.mPendingRelease)), mRecycling(aOther.mRecycling) {} AnimationImageKeyData& AnimationImageKeyData::operator=( AnimationImageKeyData&& aOther) { mPendingRelease = std::move(aOther.mPendingRelease); mRecycling = aOther.mRecycling; SharedSurfacesChild::ImageKeyData::operator=(std::move(aOther)); return *this; } AnimationImageKeyData::~AnimationImageKeyData() = default; SharedSurfacesAnimation::~SharedSurfacesAnimation() { MOZ_ASSERT(mKeys.IsEmpty()); } void SharedSurfacesAnimation::Destroy() { if (!NS_IsMainThread()) { nsCOMPtr task = NewRunnableMethod("SharedSurfacesAnimation::Destroy", this, &SharedSurfacesAnimation::Destroy); SystemGroup::Dispatch(TaskCategory::Other, task.forget()); return; } if (mKeys.IsEmpty()) { return; } for (const auto& entry : mKeys) { MOZ_ASSERT(!entry.mManager->IsDestroyed()); if (entry.mRecycling) { entry.mManager->DeregisterAsyncAnimation(entry.mImageKey); } entry.mManager->AddImageKeyForDiscard(entry.mImageKey); } mKeys.Clear(); } void SharedSurfacesAnimation::HoldSurfaceForRecycling( AnimationImageKeyData& aEntry, SourceSurface* aParentSurface, SourceSurfaceSharedData* aSurface) { if (aParentSurface == static_cast(aSurface)) { return; } if (!aEntry.mRecycling) { aEntry.mManager->RegisterAsyncAnimation(aEntry.mImageKey, this); aEntry.mRecycling = true; } aEntry.mPendingRelease.AppendElement(aParentSurface); } nsresult SharedSurfacesAnimation::SetCurrentFrame( SourceSurface* aParentSurface, SourceSurfaceSharedData* aSurface, const gfx::IntRect& aDirtyRect) { MOZ_ASSERT(aSurface); SharedSurfacesChild::SharedUserData* data = nullptr; nsresult rv = SharedSurfacesChild::ShareInternal(aSurface, &data); if (NS_FAILED(rv)) { return rv; } MOZ_ASSERT(data); mId = data->Id(); auto i = mKeys.Length(); while (i > 0) { --i; AnimationImageKeyData& entry = mKeys[i]; MOZ_ASSERT(!entry.mManager->IsDestroyed()); entry.MergeDirtyRect(Some(aDirtyRect)); Maybe dirtyRect = entry.TakeDirtyRect(); if (dirtyRect) { HoldSurfaceForRecycling(entry, aParentSurface, aSurface); auto& resourceUpdates = entry.mManager->AsyncResourceUpdates(); resourceUpdates.UpdateExternalImage(mId, entry.mImageKey, ViewAs(dirtyRect.ref())); } } return NS_OK; } nsresult SharedSurfacesAnimation::UpdateKey( SourceSurface* aParentSurface, SourceSurfaceSharedData* aSurface, RenderRootStateManager* aManager, wr::IpcResourceUpdateQueue& aResources, wr::ImageKey& aKey) { SharedSurfacesChild::SharedUserData* data = nullptr; nsresult rv = SharedSurfacesChild::ShareInternal(aSurface, &data); if (NS_FAILED(rv)) { return rv; } MOZ_ASSERT(data); if (mId.mHandle != data->Id().mHandle) { mKeys.Clear(); mId = data->Id(); } // We iterate through all of the items to ensure we clean up the old // RenderRootStateManager references. Most of the time there will be few // entries and this should not be particularly expensive compared to the // cost of duplicating image keys. In an ideal world, we would generate a // single key for the surface, and it would be usable on all of the // renderer instances. For now, we must allocate a key for each WR bridge. bool found = false; auto i = mKeys.Length(); while (i > 0) { --i; AnimationImageKeyData& entry = mKeys[i]; MOZ_ASSERT(!entry.mManager->IsDestroyed()); if (entry.mManager == aManager) { WebRenderBridgeChild* wrBridge = aManager->WrBridge(); MOZ_ASSERT(wrBridge); // Even if the manager is the same, its underlying WebRenderBridgeChild // can change state. If our namespace differs, then our old key has // already been discarded. bool ownsKey = wrBridge->GetNamespace() == entry.mImageKey.mNamespace; if (!ownsKey) { entry.mImageKey = wrBridge->GetNextImageKey(); HoldSurfaceForRecycling(entry, aParentSurface, aSurface); aResources.AddExternalImage(mId, entry.mImageKey); } else { MOZ_ASSERT(entry.mDirtyRect.isNothing()); } aKey = entry.mImageKey; found = true; break; } } if (!found) { aKey = aManager->WrBridge()->GetNextImageKey(); AnimationImageKeyData data(aManager, aKey); HoldSurfaceForRecycling(data, aParentSurface, aSurface); mKeys.AppendElement(std::move(data)); aResources.AddExternalImage(mId, aKey); } return NS_OK; } void SharedSurfacesAnimation::ReleasePreviousFrame( RenderRootStateManager* aManager, const wr::ExternalImageId& aId) { MOZ_ASSERT(aManager); auto i = mKeys.Length(); while (i > 0) { --i; AnimationImageKeyData& entry = mKeys[i]; MOZ_ASSERT(!entry.mManager->IsDestroyed()); if (entry.mManager == aManager) { size_t k; for (k = 0; k < entry.mPendingRelease.Length(); ++k) { auto sharedSurface = SharedSurfacesChild::AsSourceSurfaceSharedData( entry.mPendingRelease[k]); MOZ_ASSERT(sharedSurface); Maybe extId = SharedSurfacesChild::GetExternalId(sharedSurface); if (extId && extId.ref() == aId) { break; } } if (k == entry.mPendingRelease.Length()) { continue; } entry.mPendingRelease.RemoveElementsAt(0, k + 1); break; } } } void SharedSurfacesAnimation::Invalidate(RenderRootStateManager* aManager) { auto i = mKeys.Length(); while (i > 0) { --i; AnimationImageKeyData& entry = mKeys[i]; if (entry.mManager == aManager) { mKeys.RemoveElementAt(i); break; } } } } // namespace layers } // namespace mozilla