fune/gfx/layers/ipc/SharedSurfacesParent.cpp
Andrew Osmond e96409bd27 Bug 1869346 - Rework how canvas accesses shared surfaces in the compositor process. r=gfx-reviewers,lsalzman
This patch adds plumbing to allow sharing ExternalImageId via
SurfaceDescriptor. This will be used in a future patch by WebGL and
WebGPU to avoid extra copies. It also refactors how CanvasTranslator
gets a SourceSurfaceSharedDataWrapper pointer by ensuring the surfaces
are kept alive long enough even if the CompositorManagerParent actor is
destroyed before CanvasManagerParent and its children. Now we keep the
surfaces around until all of these dependent objects have been freed.

Differential Revision: https://phabricator.services.mozilla.com/D196067
2023-12-11 20:44:44 +00:00

405 lines
12 KiB
C++

/* -*- 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> SharedSurfacesParent::sInstance;
void SharedSurfacesParent::MappingTracker::NotifyExpiredLocked(
SourceSurfaceSharedDataWrapper* aSurface,
const StaticMutexAutoLock& aAutoLock) {
RemoveObjectLocked(aSurface, aAutoLock);
mExpired.AppendElement(aSurface);
}
void SharedSurfacesParent::MappingTracker::TakeExpired(
nsTArray<RefPtr<gfx::SourceSurfaceSharedDataWrapper>>& aExpired,
const StaticMutexAutoLock& aAutoLock) {
aExpired = std::move(mExpired);
}
void SharedSurfacesParent::MappingTracker::NotifyHandlerEnd() {
nsTArray<RefPtr<gfx::SourceSurfaceSharedDataWrapper>> 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<DataSourceSurface> SharedSurfacesParent::Get(
const wr::ExternalImageId& aId) {
RefPtr<SourceSurfaceSharedDataWrapper> 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<DataSourceSurface> SharedSurfacesParent::Acquire(
const wr::ExternalImageId& aId) {
StaticMutexAutoLock lock(sMutex);
if (!sInstance) {
gfxCriticalNote << "SSP:Acq " << wr::AsUint64(aId) << " shtd";
return nullptr;
}
RefPtr<SourceSurfaceSharedDataWrapper> surface;
sInstance->mSurfaces.Get(wr::AsUint64(aId), getter_AddRefs(surface));
if (surface) {
DebugOnly<bool> 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<SourceSurfaceSharedDataWrapper> 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<SourceSurfaceSharedDataWrapper> surface =
new SourceSurfaceSharedDataWrapper();
surface->Init(aSurface);
uint64_t id = wr::AsUint64(aId);
MOZ_ASSERT(!sInstance->mSurfaces.Contains(id));
auto texture = MakeRefPtr<wr::RenderSharedSurfaceTextureHost>(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<uint32_t>(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<SourceSurfaceSharedDataWrapper> 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<wr::RenderSharedSurfaceTextureHost>(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<bool> 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<RefPtr<SourceSurfaceSharedDataWrapper>>& 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<RefPtr<SourceSurfaceSharedDataWrapper>>& aExpired) {
StaticMutexAutoLock lock(sMutex);
if (!sInstance) {
return false;
}
return AgeOneGenerationLocked(aExpired, lock);
}
/* static */
bool SharedSurfacesParent::AgeAndExpireOneGeneration() {
nsTArray<RefPtr<SourceSurfaceSharedDataWrapper>> expired;
bool aged = AgeOneGeneration(expired);
ExpireMap(expired);
return aged;
}
/* static */
void SharedSurfacesParent::ExpireMap(
nsTArray<RefPtr<SourceSurfaceSharedDataWrapper>>& 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<uint32_t>(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