diff options
Diffstat (limited to 'gfx/ipc/CanvasManagerChild.cpp')
-rw-r--r-- | gfx/ipc/CanvasManagerChild.cpp | 338 |
1 files changed, 338 insertions, 0 deletions
diff --git a/gfx/ipc/CanvasManagerChild.cpp b/gfx/ipc/CanvasManagerChild.cpp new file mode 100644 index 0000000000..eca803bc4b --- /dev/null +++ b/gfx/ipc/CanvasManagerChild.cpp @@ -0,0 +1,338 @@ +/* -*- 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 "CanvasManagerChild.h" +#include "mozilla/AppShutdown.h" +#include "mozilla/dom/CanvasRenderingContext2D.h" +#include "mozilla/dom/WorkerPrivate.h" +#include "mozilla/dom/WorkerRef.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Swizzle.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/layers/ActiveResource.h" +#include "mozilla/layers/CanvasChild.h" +#include "mozilla/layers/CompositorManagerChild.h" +#include "mozilla/webgpu/WebGPUChild.h" + +using namespace mozilla::dom; +using namespace mozilla::layers; + +namespace mozilla::gfx { + +// The IPDL actor holds a strong reference to CanvasManagerChild which we use +// to keep it alive. The owning thread will tell us to close when it is +// shutdown, either via CanvasManagerChild::Shutdown for the main thread, or +// via a shutdown callback from ThreadSafeWorkerRef for worker threads. +MOZ_THREAD_LOCAL(CanvasManagerChild*) CanvasManagerChild::sLocalManager; + +Atomic<uint32_t> CanvasManagerChild::sNextId(1); + +CanvasManagerChild::CanvasManagerChild(uint32_t aId) : mId(aId) {} +CanvasManagerChild::~CanvasManagerChild() = default; + +void CanvasManagerChild::ActorDestroy(ActorDestroyReason aReason) { + DestroyInternal(); + if (sLocalManager.get() == this) { + sLocalManager.set(nullptr); + } + mWorkerRef = nullptr; +} + +void CanvasManagerChild::DestroyInternal() { + std::set<CanvasRenderingContext2D*> activeCanvas = std::move(mActiveCanvas); + for (const auto& i : activeCanvas) { + i->OnShutdown(); + } + + if (mActiveResourceTracker) { + mActiveResourceTracker->AgeAllGenerations(); + mActiveResourceTracker.reset(); + } + + if (mCanvasChild) { + mCanvasChild->Destroy(); + mCanvasChild = nullptr; + } +} + +void CanvasManagerChild::Destroy() { + DestroyInternal(); + + // The caller has a strong reference. ActorDestroy will clear sLocalManager + // and mWorkerRef. + Close(); +} + +/* static */ void CanvasManagerChild::Shutdown() { + MOZ_ASSERT(NS_IsMainThread()); + + // The worker threads should destroy their own CanvasManagerChild instances + // during their shutdown sequence. We just need to take care of the main + // thread. We need to init here because we may have never created a + // CanvasManagerChild for the main thread in the first place. + if (sLocalManager.init()) { + RefPtr<CanvasManagerChild> manager = sLocalManager.get(); + if (manager) { + manager->Destroy(); + } + } +} + +/* static */ bool CanvasManagerChild::CreateParent( + ipc::Endpoint<PCanvasManagerParent>&& aEndpoint) { + MOZ_ASSERT(NS_IsMainThread()); + + CompositorManagerChild* manager = CompositorManagerChild::GetInstance(); + if (!manager || !manager->CanSend()) { + return false; + } + + return manager->SendInitCanvasManager(std::move(aEndpoint)); +} + +/* static */ CanvasManagerChild* CanvasManagerChild::Get() { + if (NS_WARN_IF(!sLocalManager.init())) { + return nullptr; + } + + CanvasManagerChild* managerWeak = sLocalManager.get(); + if (managerWeak) { + return managerWeak; + } + + // We are only used on the main thread, or on worker threads. + WorkerPrivate* worker = GetCurrentThreadWorkerPrivate(); + MOZ_ASSERT_IF(!worker, NS_IsMainThread()); + + ipc::Endpoint<PCanvasManagerParent> parentEndpoint; + ipc::Endpoint<PCanvasManagerChild> childEndpoint; + + auto compositorPid = CompositorManagerChild::GetOtherPid(); + if (NS_WARN_IF(!compositorPid)) { + return nullptr; + } + + nsresult rv = PCanvasManager::CreateEndpoints( + compositorPid, base::GetCurrentProcId(), &parentEndpoint, &childEndpoint); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + auto manager = MakeRefPtr<CanvasManagerChild>(sNextId++); + + if (worker) { + // The ThreadSafeWorkerRef will let us know when the worker is shutting + // down. This will let us clear our threadlocal reference and close the + // actor. We rely upon an explicit shutdown for the main thread. + RefPtr<StrongWorkerRef> workerRef = StrongWorkerRef::Create( + worker, "CanvasManager", [manager]() { manager->Destroy(); }); + if (NS_WARN_IF(!workerRef)) { + return nullptr; + } + + manager->mWorkerRef = new ThreadSafeWorkerRef(workerRef); + } else if (NS_IsMainThread()) { + if (NS_WARN_IF( + AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed))) { + return nullptr; + } + } else { + MOZ_ASSERT_UNREACHABLE("Can only be used on main or DOM worker threads!"); + return nullptr; + } + + if (NS_WARN_IF(!childEndpoint.Bind(manager))) { + return nullptr; + } + + // We can't talk to the compositor process directly from worker threads, but + // the main thread can via CompositorManagerChild. + if (worker) { + worker->DispatchToMainThread(NS_NewRunnableFunction( + "CanvasManagerChild::CreateParent", + [parentEndpoint = std::move(parentEndpoint)]() { + CreateParent( + std::move(const_cast<ipc::Endpoint<PCanvasManagerParent>&&>( + parentEndpoint))); + })); + } else if (NS_WARN_IF(!CreateParent(std::move(parentEndpoint)))) { + return nullptr; + } + + manager->SendInitialize(manager->Id()); + sLocalManager.set(manager); + return manager; +} + +/* static */ CanvasManagerChild* CanvasManagerChild::MaybeGet() { + if (!sLocalManager.initialized()) { + return nullptr; + } + + return sLocalManager.get(); +} + +void CanvasManagerChild::AddShutdownObserver( + dom::CanvasRenderingContext2D* aCanvas) { + mActiveCanvas.insert(aCanvas); +} + +void CanvasManagerChild::RemoveShutdownObserver( + dom::CanvasRenderingContext2D* aCanvas) { + mActiveCanvas.erase(aCanvas); +} + +void CanvasManagerChild::EndCanvasTransaction() { + if (!mCanvasChild) { + return; + } + + mCanvasChild->EndTransaction(); + if (mCanvasChild->ShouldBeCleanedUp()) { + mCanvasChild->Destroy(); + mCanvasChild = nullptr; + } +} + +void CanvasManagerChild::ClearCachedResources() { + if (mCanvasChild) { + mCanvasChild->ClearCachedResources(); + } +} + +void CanvasManagerChild::DeactivateCanvas() { + mActive = false; + if (mCanvasChild) { + mCanvasChild->Destroy(); + mCanvasChild = nullptr; + } +} + +void CanvasManagerChild::BlockCanvas() { mBlocked = true; } + +RefPtr<layers::CanvasChild> CanvasManagerChild::GetCanvasChild() { + if (mBlocked) { + return nullptr; + } + + if (!mActive) { + MOZ_ASSERT(!mCanvasChild); + return nullptr; + } + + if (!mCanvasChild) { + mCanvasChild = MakeAndAddRef<layers::CanvasChild>(); + if (!SendPCanvasConstructor(mCanvasChild)) { + mCanvasChild = nullptr; + } + } + + return mCanvasChild; +} + +RefPtr<webgpu::WebGPUChild> CanvasManagerChild::GetWebGPUChild() { + if (!mWebGPUChild) { + mWebGPUChild = MakeAndAddRef<webgpu::WebGPUChild>(); + if (!SendPWebGPUConstructor(mWebGPUChild)) { + mWebGPUChild = nullptr; + } + } + + return mWebGPUChild; +} + +layers::ActiveResourceTracker* CanvasManagerChild::GetActiveResourceTracker() { + if (!mActiveResourceTracker) { + mActiveResourceTracker = MakeUnique<ActiveResourceTracker>( + 1000, "CanvasManagerChild", GetCurrentSerialEventTarget()); + } + return mActiveResourceTracker.get(); +} + +already_AddRefed<DataSourceSurface> CanvasManagerChild::GetSnapshot( + uint32_t aManagerId, int32_t aProtocolId, + const Maybe<RemoteTextureOwnerId>& aOwnerId, SurfaceFormat aFormat, + bool aPremultiply, bool aYFlip) { + if (!CanSend()) { + return nullptr; + } + + webgl::FrontBufferSnapshotIpc res; + if (!SendGetSnapshot(aManagerId, aProtocolId, aOwnerId, &res)) { + return nullptr; + } + + if (!res.shmem || !res.shmem->IsReadable()) { + return nullptr; + } + + auto guard = MakeScopeExit([&] { DeallocShmem(res.shmem.ref()); }); + + if (!res.surfSize.x || !res.surfSize.y || res.surfSize.x > INT32_MAX || + res.surfSize.y > INT32_MAX) { + return nullptr; + } + + IntSize size(res.surfSize.x, res.surfSize.y); + CheckedInt32 stride = CheckedInt32(size.width) * sizeof(uint32_t); + if (!stride.isValid()) { + return nullptr; + } + + CheckedInt32 length = stride * size.height; + if (!length.isValid() || + size_t(length.value()) != res.shmem->Size<uint8_t>()) { + return nullptr; + } + + SurfaceFormat format = + IsOpaque(aFormat) ? SurfaceFormat::B8G8R8X8 : SurfaceFormat::B8G8R8A8; + RefPtr<DataSourceSurface> surface = + Factory::CreateDataSourceSurfaceWithStride(size, format, stride.value(), + /* aZero */ false); + if (!surface) { + return nullptr; + } + + gfx::DataSourceSurface::ScopedMap map(surface, + gfx::DataSourceSurface::READ_WRITE); + if (!map.IsMapped()) { + return nullptr; + } + + // The buffer we may readback from the canvas could be R8G8B8A8, not + // premultiplied, and/or has its rows iverted. For the general case, we want + // surfaces represented as premultiplied B8G8R8A8, with its rows ordered top + // to bottom. Given this path is used for screenshots/SurfaceFromElement, + // that's the representation we need. + if (aYFlip) { + if (aPremultiply) { + if (!PremultiplyYFlipData(res.shmem->get<uint8_t>(), stride.value(), + aFormat, map.GetData(), map.GetStride(), format, + size)) { + return nullptr; + } + } else { + if (!SwizzleYFlipData(res.shmem->get<uint8_t>(), stride.value(), aFormat, + map.GetData(), map.GetStride(), format, size)) { + return nullptr; + } + } + } else if (aPremultiply) { + if (!PremultiplyData(res.shmem->get<uint8_t>(), stride.value(), aFormat, + map.GetData(), map.GetStride(), format, size)) { + return nullptr; + } + } else { + if (!SwizzleData(res.shmem->get<uint8_t>(), stride.value(), aFormat, + map.GetData(), map.GetStride(), format, size)) { + return nullptr; + } + } + return surface.forget(); +} + +} // namespace mozilla::gfx |