/* -*- 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 "SharedSurfacesParent.h" #include "mozilla/DebugOnly.h" #include "mozilla/StaticPrefs_image.h" #include "mozilla/gfx/Logging.h" #include "mozilla/gfx/gfxVars.h" #include "mozilla/gfx/GPUProcessManager.h" #include "mozilla/layers/SharedSurfacesMemoryReport.h" #include "mozilla/layers/SourceSurfaceSharedData.h" #include "mozilla/layers/CompositorManagerParent.h" #include "mozilla/layers/CompositorThread.h" #include "mozilla/webrender/RenderSharedSurfaceTextureHost.h" #include "mozilla/webrender/RenderThread.h" #include "nsThreadUtils.h" // for GetCurrentSerialEventTarget namespace mozilla { namespace layers { using namespace mozilla::gfx; StaticMutex SharedSurfacesParent::sMutex; StaticAutoPtr SharedSurfacesParent::sInstance; void SharedSurfacesParent::MappingTracker::NotifyExpiredLocked( SourceSurfaceSharedDataWrapper* aSurface, const StaticMutexAutoLock& aAutoLock) { RemoveObjectLocked(aSurface, aAutoLock); mExpired.AppendElement(aSurface); } void SharedSurfacesParent::MappingTracker::TakeExpired( nsTArray>& aExpired, const StaticMutexAutoLock& aAutoLock) { aExpired = std::move(mExpired); } void SharedSurfacesParent::MappingTracker::NotifyHandlerEnd() { nsTArray> expired; { StaticMutexAutoLock lock(sMutex); TakeExpired(expired, lock); } SharedSurfacesParent::ExpireMap(expired); } SharedSurfacesParent::SharedSurfacesParent() : mTracker( StaticPrefs::image_mem_shared_unmap_min_expiration_ms_AtStartup(), mozilla::GetCurrentSerialEventTarget()) {} /* static */ void SharedSurfacesParent::Initialize() { MOZ_ASSERT(NS_IsMainThread()); StaticMutexAutoLock lock(sMutex); if (!sInstance) { sInstance = new SharedSurfacesParent(); } } /* static */ void SharedSurfacesParent::ShutdownRenderThread() { // The main thread should blocked on waiting for the render thread to // complete so this should be safe to release off the main thread. MOZ_ASSERT(wr::RenderThread::IsInRenderThread()); StaticMutexAutoLock lock(sMutex); MOZ_ASSERT(sInstance); for (const auto& key : sInstance->mSurfaces.Keys()) { // There may be lingering consumers of the surfaces that didn't get shutdown // yet but since we are here, we know the render thread is finished and we // can unregister everything. wr::RenderThread::Get()->UnregisterExternalImageDuringShutdown( wr::ToExternalImageId(key)); } } /* static */ void SharedSurfacesParent::Shutdown() { // The compositor thread and render threads are shutdown, so this is the last // thread that could use it. The expiration tracker needs to be freed on the // main thread. MOZ_ASSERT(NS_IsMainThread()); StaticMutexAutoLock lock(sMutex); sInstance = nullptr; } /* static */ already_AddRefed SharedSurfacesParent::Get( const wr::ExternalImageId& aId) { RefPtr surface; { StaticMutexAutoLock lock(sMutex); if (!sInstance) { gfxCriticalNote << "SSP:Get " << wr::AsUint64(aId) << " shtd"; return nullptr; } if (sInstance->mSurfaces.Get(wr::AsUint64(aId), getter_AddRefs(surface))) { return surface.forget(); } } // We cannot block the compositor thread since that's the thread the necessary // IPDL events would come in on. if (NS_WARN_IF(CompositorThreadHolder::IsInCompositorThread())) { return nullptr; } // Block until we see the relevant resource come in or the actor is destroyed. CompositorManagerParent::WaitForSharedSurface(aId); StaticMutexAutoLock lock(sMutex); if (!sInstance) { gfxCriticalNote << "SSP:Get " << wr::AsUint64(aId) << " shtd"; return nullptr; } sInstance->mSurfaces.Get(wr::AsUint64(aId), getter_AddRefs(surface)); return surface.forget(); } /* static */ already_AddRefed SharedSurfacesParent::Acquire( const wr::ExternalImageId& aId) { StaticMutexAutoLock lock(sMutex); if (!sInstance) { gfxCriticalNote << "SSP:Acq " << wr::AsUint64(aId) << " shtd"; return nullptr; } RefPtr surface; sInstance->mSurfaces.Get(wr::AsUint64(aId), getter_AddRefs(surface)); if (surface) { DebugOnly rv = surface->AddConsumer(); MOZ_ASSERT(!rv); } return surface.forget(); } /* static */ bool SharedSurfacesParent::Release(const wr::ExternalImageId& aId, bool aForCreator) { StaticMutexAutoLock lock(sMutex); if (!sInstance) { return false; } uint64_t id = wr::AsUint64(aId); RefPtr surface; sInstance->mSurfaces.Get(wr::AsUint64(aId), getter_AddRefs(surface)); if (!surface) { return false; } if (surface->RemoveConsumer(aForCreator)) { RemoveTrackingLocked(surface, lock); wr::RenderThread::Get()->UnregisterExternalImage(wr::ToExternalImageId(id)); sInstance->mSurfaces.Remove(id); } return true; } /* static */ void SharedSurfacesParent::AddSameProcess(const wr::ExternalImageId& aId, SourceSurfaceSharedData* aSurface) { MOZ_ASSERT(XRE_IsParentProcess()); MOZ_ASSERT(NS_IsMainThread()); StaticMutexAutoLock lock(sMutex); if (!sInstance) { gfxCriticalNote << "SSP:Ads " << wr::AsUint64(aId) << " shtd"; return; } // If the child bridge detects it is in the combined UI/GPU process, then it // will insert a wrapper surface holding the shared memory buffer directly. // This is good because we avoid mapping the same shared memory twice, but // still allow the original surface to be freed and remove the wrapper from // the table when it is no longer needed. RefPtr surface = new SourceSurfaceSharedDataWrapper(); surface->Init(aSurface); uint64_t id = wr::AsUint64(aId); MOZ_ASSERT(!sInstance->mSurfaces.Contains(id)); auto texture = MakeRefPtr(surface); wr::RenderThread::Get()->RegisterExternalImage(aId, texture.forget()); surface->AddConsumer(); sInstance->mSurfaces.InsertOrUpdate(id, std::move(surface)); } /* static */ void SharedSurfacesParent::RemoveAll(uint32_t aNamespace) { StaticMutexAutoLock lock(sMutex); if (!sInstance) { return; } auto* renderThread = wr::RenderThread::Get(); // Note that the destruction of a parent may not be cheap if it still has a // lot of surfaces still bound that require unmapping. for (auto i = sInstance->mSurfaces.Iter(); !i.Done(); i.Next()) { if (static_cast(i.Key() >> 32) != aNamespace) { continue; } SourceSurfaceSharedDataWrapper* surface = i.Data(); if (surface->HasCreatorRef() && surface->RemoveConsumer(/* aForCreator */ true)) { RemoveTrackingLocked(surface, lock); if (renderThread) { renderThread->UnregisterExternalImage(wr::ToExternalImageId(i.Key())); } i.Remove(); } } } /* static */ void SharedSurfacesParent::Add(const wr::ExternalImageId& aId, SurfaceDescriptorShared&& aDesc, base::ProcessId aPid) { MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); MOZ_ASSERT(aPid != base::GetCurrentProcId()); RefPtr surface = new SourceSurfaceSharedDataWrapper(); // We preferentially map in new surfaces when they are initially received // because we are likely to reference them in a display list soon. The unmap // will ensure we add the surface to the expiration tracker. We do it outside // the mutex to ensure we always lock the surface mutex first, and our mutex // second, to avoid deadlock. // // Note that the surface wrapper maps in the given handle as read only. surface->Init(aDesc.size(), aDesc.stride(), aDesc.format(), std::move(aDesc.handle()), aPid); StaticMutexAutoLock lock(sMutex); if (!sInstance) { gfxCriticalNote << "SSP:Add " << wr::AsUint64(aId) << " shtd"; return; } uint64_t id = wr::AsUint64(aId); MOZ_ASSERT(!sInstance->mSurfaces.Contains(id)); auto texture = MakeRefPtr(surface); wr::RenderThread::Get()->RegisterExternalImage(aId, texture.forget()); surface->AddConsumer(); sInstance->mSurfaces.InsertOrUpdate(id, std::move(surface)); } /* static */ void SharedSurfacesParent::Remove(const wr::ExternalImageId& aId) { DebugOnly rv = Release(aId, /* aForCreator */ true); MOZ_ASSERT(rv); } /* static */ void SharedSurfacesParent::AddTrackingLocked( SourceSurfaceSharedDataWrapper* aSurface, const StaticMutexAutoLock& aAutoLock) { MOZ_ASSERT(!aSurface->GetExpirationState()->IsTracked()); sInstance->mTracker.AddObjectLocked(aSurface, aAutoLock); } /* static */ void SharedSurfacesParent::AddTracking( SourceSurfaceSharedDataWrapper* aSurface) { StaticMutexAutoLock lock(sMutex); if (!sInstance) { return; } AddTrackingLocked(aSurface, lock); } /* static */ void SharedSurfacesParent::RemoveTrackingLocked( SourceSurfaceSharedDataWrapper* aSurface, const StaticMutexAutoLock& aAutoLock) { if (!aSurface->GetExpirationState()->IsTracked()) { return; } sInstance->mTracker.RemoveObjectLocked(aSurface, aAutoLock); } /* static */ void SharedSurfacesParent::RemoveTracking( SourceSurfaceSharedDataWrapper* aSurface) { StaticMutexAutoLock lock(sMutex); if (!sInstance) { return; } RemoveTrackingLocked(aSurface, lock); } /* static */ bool SharedSurfacesParent::AgeOneGenerationLocked( nsTArray>& aExpired, const StaticMutexAutoLock& aAutoLock) { if (sInstance->mTracker.IsEmptyLocked(aAutoLock)) { return false; } sInstance->mTracker.AgeOneGenerationLocked(aAutoLock); sInstance->mTracker.TakeExpired(aExpired, aAutoLock); return true; } /* static */ bool SharedSurfacesParent::AgeOneGeneration( nsTArray>& aExpired) { StaticMutexAutoLock lock(sMutex); if (!sInstance) { return false; } return AgeOneGenerationLocked(aExpired, lock); } /* static */ bool SharedSurfacesParent::AgeAndExpireOneGeneration() { nsTArray> expired; bool aged = AgeOneGeneration(expired); ExpireMap(expired); return aged; } /* static */ void SharedSurfacesParent::ExpireMap( nsTArray>& aExpired) { for (auto& surface : aExpired) { surface->ExpireMap(); } } /* static */ void SharedSurfacesParent::AccumulateMemoryReport( uint32_t aNamespace, SharedSurfacesMemoryReport& aReport) { StaticMutexAutoLock lock(sMutex); if (!sInstance) { return; } for (const auto& entry : sInstance->mSurfaces) { if (static_cast(entry.GetKey() >> 32) != aNamespace) { continue; } SourceSurfaceSharedDataWrapper* surface = entry.GetData(); aReport.mSurfaces.insert(std::make_pair( entry.GetKey(), SharedSurfacesMemoryReport::SurfaceEntry{ surface->GetCreatorPid(), surface->GetSize(), surface->Stride(), surface->GetConsumers(), surface->HasCreatorRef()})); } } /* static */ bool SharedSurfacesParent::AccumulateMemoryReport( SharedSurfacesMemoryReport& aReport) { if (XRE_IsParentProcess()) { GPUProcessManager* gpm = GPUProcessManager::Get(); if (!gpm || gpm->GPUProcessPid() != base::kInvalidProcessId) { return false; } } else if (!XRE_IsGPUProcess()) { return false; } StaticMutexAutoLock lock(sMutex); if (!sInstance) { return true; } for (const auto& entry : sInstance->mSurfaces) { SourceSurfaceSharedDataWrapper* surface = entry.GetData(); aReport.mSurfaces.insert(std::make_pair( entry.GetKey(), SharedSurfacesMemoryReport::SurfaceEntry{ surface->GetCreatorPid(), surface->GetSize(), surface->Stride(), surface->GetConsumers(), surface->HasCreatorRef()})); } return true; } } // namespace layers } // namespace mozilla