Bug 1736177 - Part 9. Add plumbing to snapshot worker canvas contexts from the main thread. r=jgilbert,ipc-reviewers,nika

Right now, if we wanted to get a snapshot of an OffscreenCanvas context
on a worker thread from the main thread, we would need to block the main
thread on the worker thread, and from the worker thread, issue a sync
IPC call get the front buffer snapshot.

This patch adds an alternative which allows the main thread to go
directly to the canvas owning thread in the compositor process to get
the snapshot, thereby bypassing the worker thread in content process
entirely. All it needs as the unique ID of the CanvasManagerChild
instance, and the protocol ID of the WebGLChild instance.

This will be used for Firefox screenshots, New Tab tiles, and printing.

Differential Revision: https://phabricator.services.mozilla.com/D130785
This commit is contained in:
Andrew Osmond 2021-12-09 19:25:28 +00:00
Родитель 8cd6585103
Коммит 8077b44a0c
8 изменённых файлов: 145 добавлений и 7 удалений

Просмотреть файл

@ -88,10 +88,15 @@ void WebGLParent::ActorDestroy(ActorDestroyReason aWhy) { mHost = nullptr; }
IPCResult WebGLParent::RecvGetFrontBufferSnapshot(
webgl::FrontBufferSnapshotIpc* const ret) {
return GetFrontBufferSnapshot(ret, this);
}
IPCResult WebGLParent::GetFrontBufferSnapshot(
webgl::FrontBufferSnapshotIpc* const ret, IProtocol* aProtocol) {
*ret = {};
if (!mHost) {
return IPC_FAIL(this, "HostWebGLContext is not initialized.");
return IPC_FAIL(aProtocol, "HostWebGLContext is not initialized.");
}
const auto maybeSize = mHost->FrontBufferSnapshotInto({});
@ -100,11 +105,11 @@ IPCResult WebGLParent::RecvGetFrontBufferSnapshot(
const auto byteSize = 4 * surfSize.x * surfSize.y;
auto shmem = webgl::RaiiShmem::Alloc(
this, byteSize,
aProtocol, byteSize,
mozilla::ipc::SharedMemory::SharedMemoryType::TYPE_BASIC);
if (!shmem) {
NS_WARNING("Failed to alloc shmem for RecvGetFrontBufferSnapshot.");
return IPC_FAIL(this, "Failed to allocate shmem for result");
return IPC_FAIL(aProtocol, "Failed to allocate shmem for result");
}
const auto range = shmem.ByteRange();
*ret = {surfSize, Some(shmem.Extract())};

Просмотреть файл

@ -39,6 +39,8 @@ class WebGLParent : public PWebGLParent, public SupportsWeakPtr {
IPCResult RecvGetBufferSubData(GLenum target, uint64_t srcByteOffset,
uint64_t byteSize, mozilla::ipc::Shmem* ret);
IPCResult GetFrontBufferSnapshot(webgl::FrontBufferSnapshotIpc* ret,
IProtocol* aProtocol);
IPCResult RecvGetFrontBufferSnapshot(webgl::FrontBufferSnapshotIpc* ret);
IPCResult RecvReadPixels(const webgl::ReadPixelsDesc&, uint64_t byteSize,
webgl::ReadPixelsResultIpc* ret);

Просмотреть файл

@ -7,6 +7,7 @@
#include "CanvasManagerChild.h"
#include "mozilla/dom/WorkerPrivate.h"
#include "mozilla/dom/WorkerRef.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/ipc/Endpoint.h"
#include "mozilla/layers/CompositorManagerChild.h"
@ -21,7 +22,9 @@ namespace mozilla::gfx {
// via a shutdown callback from IPCWorkerRef for worker threads.
MOZ_THREAD_LOCAL(CanvasManagerChild*) CanvasManagerChild::sLocalManager;
CanvasManagerChild::CanvasManagerChild() = default;
Atomic<uint32_t> CanvasManagerChild::sNextId(1);
CanvasManagerChild::CanvasManagerChild(uint32_t aId) : mId(aId) {}
CanvasManagerChild::~CanvasManagerChild() = default;
void CanvasManagerChild::ActorDestroy(ActorDestroyReason aReason) {
@ -91,7 +94,7 @@ void CanvasManagerChild::Destroy() {
return nullptr;
}
auto manager = MakeRefPtr<CanvasManagerChild>();
auto manager = MakeRefPtr<CanvasManagerChild>(sNextId++);
if (worker) {
// The IPCWorkerRef will let us know when the worker is shutting down. This
@ -122,8 +125,61 @@ void CanvasManagerChild::Destroy() {
return nullptr;
}
manager->SendInitialize(manager->Id());
sLocalManager.set(manager);
return manager;
}
already_AddRefed<DataSourceSurface> CanvasManagerChild::GetSnapshot(
uint32_t aManagerId, int32_t aProtocolId, bool aHasAlpha) {
if (!CanSend()) {
return nullptr;
}
webgl::FrontBufferSnapshotIpc res;
if (!SendGetSnapshot(aManagerId, aProtocolId, &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;
}
RefPtr<DataSourceSurface> surface =
Factory::CreateDataSourceSurfaceWithStride(size, SurfaceFormat::B8G8R8A8,
stride.value(),
/* aZero */ false);
if (!surface) {
return nullptr;
}
gfx::DataSourceSurface::ScopedMap map(surface,
gfx::DataSourceSurface::READ_WRITE);
if (!map.IsMapped()) {
return nullptr;
}
memcpy(map.GetData(), res.shmem->get<uint8_t>(), res.shmem->Size<uint8_t>());
return surface.forget();
}
} // namespace mozilla::gfx

Просмотреть файл

@ -6,6 +6,7 @@
#ifndef _include_gfx_ipc_CanvasManagerChild_h__
#define _include_gfx_ipc_CanvasManagerChild_h__
#include "mozilla/Atomics.h"
#include "mozilla/gfx/PCanvasManagerChild.h"
#include "mozilla/ThreadLocal.h"
@ -16,12 +17,17 @@ class WorkerPrivate;
} // namespace dom
namespace gfx {
class DataSourceSurface;
class CanvasManagerChild final : public PCanvasManagerChild {
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CanvasManagerChild, override);
CanvasManagerChild();
explicit CanvasManagerChild(uint32_t aId);
uint32_t Id() const { return mId; }
already_AddRefed<DataSourceSurface> GetSnapshot(uint32_t aManagerId,
int32_t aProtocolId,
bool aHasAlpha);
void ActorDestroy(ActorDestroyReason aReason) override;
static CanvasManagerChild* Get();
@ -34,8 +40,10 @@ class CanvasManagerChild final : public PCanvasManagerChild {
void Destroy();
RefPtr<mozilla::dom::IPCWorkerRef> mWorkerRef;
const uint32_t mId;
static MOZ_THREAD_LOCAL(CanvasManagerChild*) sLocalManager;
static Atomic<uint32_t> sNextId;
};
} // namespace gfx

Просмотреть файл

@ -87,4 +87,52 @@ already_AddRefed<dom::PWebGLParent> CanvasManagerParent::AllocPWebGLParent() {
return MakeAndAddRef<dom::WebGLParent>();
}
mozilla::ipc::IPCResult CanvasManagerParent::RecvInitialize(
const uint32_t& aId) {
if (!aId) {
return IPC_FAIL(this, "invalid id");
}
if (mId) {
return IPC_FAIL(this, "already initialized");
}
mId = aId;
return IPC_OK();
}
mozilla::ipc::IPCResult CanvasManagerParent::RecvGetSnapshot(
const uint32_t& aManagerId, const int32_t& aProtocolId,
webgl::FrontBufferSnapshotIpc* aResult) {
if (!aManagerId) {
return IPC_FAIL(this, "invalid id");
}
IProtocol* actor = nullptr;
for (CanvasManagerParent* i : sManagers) {
if (i->OtherPidMaybeInvalid() == OtherPidMaybeInvalid() &&
i->mId == aManagerId) {
actor = i->Lookup(aProtocolId);
break;
}
}
if (!actor) {
return IPC_FAIL(this, "invalid actor");
}
if (actor->GetProtocolId() != ProtocolId::PWebGLMsgStart ||
actor->GetSide() != mozilla::ipc::Side::ParentSide) {
return IPC_FAIL(this, "unsupported actor");
}
RefPtr<dom::WebGLParent> webgl = static_cast<dom::WebGLParent*>(actor);
webgl::FrontBufferSnapshotIpc buffer;
mozilla::ipc::IPCResult rv = webgl->GetFrontBufferSnapshot(&buffer, this);
if (!rv) {
return rv;
}
*aResult = std::move(buffer);
return IPC_OK();
}
} // namespace mozilla::gfx

Просмотреть файл

@ -24,11 +24,17 @@ class CanvasManagerParent final : public PCanvasManagerParent {
void Bind(Endpoint<PCanvasManagerParent>&& aEndpoint);
void ActorDestroy(ActorDestroyReason aWhy) override;
already_AddRefed<PWebGLParent> AllocPWebGLParent();
mozilla::ipc::IPCResult RecvInitialize(const uint32_t& aId);
mozilla::ipc::IPCResult RecvGetSnapshot(
const uint32_t& aManagerId, const int32_t& aProtocolId,
webgl::FrontBufferSnapshotIpc* aResult);
private:
static void ShutdownInternal();
~CanvasManagerParent();
~CanvasManagerParent() override;
uint32_t mId = 0;
using ManagerSet = nsTHashSet<CanvasManagerParent*>;
static ManagerSet sManagers;

Просмотреть файл

@ -7,6 +7,8 @@
include protocol PWebGL;
using mozilla::webgl::FrontBufferSnapshotIpc from "mozilla/dom/WebGLIpdl.h";
namespace mozilla {
namespace gfx {
@ -23,6 +25,15 @@ namespace gfx {
parent:
// Actor that represents one WebGL context.
async PWebGL();
// Set the local manager ID for the canvas manager.
async Initialize(uint32_t aManagerId);
// Get the front buffer pixels for the given manager/protocol. This is
// intended to be used by the main thread in the content process to block
// reading without having to block on the worker thread that owns the context
// instance.
sync GetSnapshot(uint32_t aManagerId, int32_t aProtocolId) returns (FrontBufferSnapshotIpc ret);
};
} // gfx

Просмотреть файл

@ -888,6 +888,8 @@ description = legacy sync IPC - please add detailed description
description = legacy sync IPC - please add detailed description
[PAPZInputBridge::ReceiveKeyboardInputEvent]
description = legacy sync IPC - please add detailed description
[PCanvasManager::GetSnapshot]
description = Retrieving canvas contents is synchronous (see also, PWebGL::GetFrontBufferSnapshot).
[PCompositorBridge::Initialize]
description = legacy sync IPC - please add detailed description
[PCompositorBridge::WillClose]