Bug 1755704 - Part 1. Add support for reading back the front buffer for WebGPU. r=gfx-reviewers,lsalzman

This reworks the CanvasManagerChild::GetSnapshot method to work with
WebGPU. This will allow it to be called for a WebGPU context from any
thread, which is useful for screenshots.

Differential Revision: https://phabricator.services.mozilla.com/D144308
This commit is contained in:
Andrew Osmond 2022-04-22 15:05:42 +00:00
Родитель 49e2102b48
Коммит 720de7a009
9 изменённых файлов: 103 добавлений и 31 удалений

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

@ -252,12 +252,14 @@ OffscreenCanvasDisplayHelper::GetSurfaceSnapshot() {
Maybe<int32_t> childId;
HTMLCanvasElement* canvasElement;
RefPtr<gfx::SourceSurface> surface;
layers::CompositableHandle handle;
{
MutexAutoLock lock(mMutex);
hasAlpha = !mData.mIsOpaque;
isAlphaPremult = mData.mIsAlphaPremult;
originPos = mData.mOriginPos;
handle = mData.mHandle;
managerId = mContextManagerId;
childId = mContextChildId;
canvasElement = mCanvasElement;
@ -311,7 +313,9 @@ OffscreenCanvasDisplayHelper::GetSurfaceSnapshot() {
// We don't have a usable surface, and the context lives in the compositor
// process.
return gfx::CanvasManagerChild::Get()->GetSnapshot(
managerId.value(), childId.value(), hasAlpha);
managerId.value(), childId.value(), handle,
hasAlpha ? gfx::SurfaceFormat::R8G8B8A8 : gfx::SurfaceFormat::R8G8B8X8,
hasAlpha && !isAlphaPremult, originPos == gl::OriginPos::BottomLeft);
}
// If we don't have any protocol IDs, or an existing surface, it is possible

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

@ -78,6 +78,7 @@ void CanvasContext::Configure(const dom::GPUCanvasConfiguration& aDesc) {
} else if (mOffscreenCanvas) {
dom::OffscreenCanvasDisplayData data;
data.mSize = {mWidth, mHeight};
data.mIsOpaque = false;
data.mHandle = mHandle;
mOffscreenCanvas->UpdateDisplayData(data);
}

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

@ -640,6 +640,36 @@ static void PresentCallback(ffi::WGPUBufferMapAsyncStatus status,
delete req;
}
ipc::IPCResult WebGPUParent::GetFrontBufferSnapshot(
IProtocol* aProtocol, const CompositableHandle& aHandle,
Maybe<Shmem>& aShmem, gfx::IntSize& aSize) {
const auto& lookup = mCanvasMap.find(aHandle.Value());
if (lookup == mCanvasMap.end()) {
return IPC_OK();
}
RefPtr<PresentationData> data = lookup->second.get();
aSize = data->mTextureHost->GetSize();
uint32_t stride =
aSize.width * BytesPerPixel(data->mTextureHost->GetFormat());
uint32_t len = data->mRowCount * stride;
Shmem shmem;
if (!AllocShmem(len, ipc::Shmem::SharedMemory::TYPE_BASIC, &shmem)) {
return IPC_OK();
}
uint8_t* dst = shmem.get<uint8_t>();
uint8_t* src = data->mTextureHost->GetBuffer();
for (uint32_t row = 0; row < data->mRowCount; ++row) {
memcpy(dst, src, stride);
src += data->mTargetPitch;
dst += stride;
}
aShmem.emplace(std::move(shmem));
return IPC_OK();
}
ipc::IPCResult WebGPUParent::RecvSwapChainPresent(
const CompositableHandle& aHandle, RawId aTextureId,
RawId aCommandEncoderId) {

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

@ -11,8 +11,7 @@
#include "WebGPUTypes.h"
#include "base/timer.h"
namespace mozilla {
namespace webgpu {
namespace mozilla::webgpu {
class ErrorBuffer;
class PresentationData;
@ -90,6 +89,11 @@ class WebGPUParent final : public PWebGPUParent {
ipc::IPCResult RecvDevicePopErrorScope(
RawId aSelfId, DevicePopErrorScopeResolver&& aResolver);
ipc::IPCResult GetFrontBufferSnapshot(IProtocol* aProtocol,
const CompositableHandle& aHandle,
Maybe<Shmem>& aShmem,
gfx::IntSize& aSize);
void ActorDestroy(ActorDestroyReason aWhy) override;
private:
@ -109,7 +113,6 @@ class WebGPUParent final : public PWebGPUParent {
std::unordered_map<uint64_t, ErrorScopeStack> mErrorScopeMap;
};
} // namespace webgpu
} // namespace mozilla
} // namespace mozilla::webgpu
#endif // WEBGPU_PARENT_H_

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

@ -144,13 +144,15 @@ RefPtr<webgpu::WebGPUChild> CanvasManagerChild::GetWebGPUChild() {
}
already_AddRefed<DataSourceSurface> CanvasManagerChild::GetSnapshot(
uint32_t aManagerId, int32_t aProtocolId, bool aHasAlpha) {
uint32_t aManagerId, int32_t aProtocolId,
const layers::CompositableHandle& aHandle, SurfaceFormat aFormat,
bool aPremultiply, bool aYFlip) {
if (!CanSend()) {
return nullptr;
}
webgl::FrontBufferSnapshotIpc res;
if (!SendGetSnapshot(aManagerId, aProtocolId, &res)) {
if (!SendGetSnapshot(aManagerId, aProtocolId, aHandle, &res)) {
return nullptr;
}
@ -178,7 +180,7 @@ already_AddRefed<DataSourceSurface> CanvasManagerChild::GetSnapshot(
}
SurfaceFormat format =
aHasAlpha ? SurfaceFormat::B8G8R8A8 : SurfaceFormat::B8G8R8X8;
IsOpaque(aFormat) ? SurfaceFormat::B8G8R8X8 : SurfaceFormat::B8G8R8A8;
RefPtr<DataSourceSurface> surface =
Factory::CreateDataSourceSurfaceWithStride(size, format, stride.value(),
/* aZero */ false);
@ -192,21 +194,32 @@ already_AddRefed<DataSourceSurface> CanvasManagerChild::GetSnapshot(
return nullptr;
}
// The buffer we read back from WebGL is R8G8B8A8, not premultiplied and has
// its rows inverted. 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 (aHasAlpha) {
if (!PremultiplyYFlipData(res.shmem->get<uint8_t>(), stride.value(),
SurfaceFormat::R8G8B8A8, map.GetData(),
map.GetStride(), format, size)) {
// 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 (!SwizzleYFlipData(res.shmem->get<uint8_t>(), stride.value(),
SurfaceFormat::R8G8B8X8, map.GetData(),
map.GetStride(), format, size)) {
if (!SwizzleData(res.shmem->get<uint8_t>(), stride.value(), aFormat,
map.GetData(), map.GetStride(), format, size)) {
return nullptr;
}
}

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

@ -8,6 +8,7 @@
#include "mozilla/Atomics.h"
#include "mozilla/gfx/PCanvasManagerChild.h"
#include "mozilla/gfx/Types.h"
#include "mozilla/ThreadLocal.h"
namespace mozilla {
@ -29,9 +30,10 @@ class CanvasManagerChild final : public PCanvasManagerChild {
explicit CanvasManagerChild(uint32_t aId);
uint32_t Id() const { return mId; }
already_AddRefed<DataSourceSurface> GetSnapshot(uint32_t aManagerId,
int32_t aProtocolId,
bool aHasAlpha);
already_AddRefed<DataSourceSurface> GetSnapshot(
uint32_t aManagerId, int32_t aProtocolId,
const layers::CompositableHandle& aHandle, SurfaceFormat aFormat,
bool aPremultiply, bool aYFlip);
void ActorDestroy(ActorDestroyReason aReason) override;
static CanvasManagerChild* Get();

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

@ -111,7 +111,7 @@ mozilla::ipc::IPCResult CanvasManagerParent::RecvInitialize(
mozilla::ipc::IPCResult CanvasManagerParent::RecvGetSnapshot(
const uint32_t& aManagerId, const int32_t& aProtocolId,
webgl::FrontBufferSnapshotIpc* aResult) {
const CompositableHandle& aHandle, webgl::FrontBufferSnapshotIpc* aResult) {
if (!aManagerId) {
return IPC_FAIL(this, "invalid id");
}
@ -129,16 +129,33 @@ mozilla::ipc::IPCResult CanvasManagerParent::RecvGetSnapshot(
return IPC_FAIL(this, "invalid actor");
}
if (actor->GetProtocolId() != ProtocolId::PWebGLMsgStart ||
actor->GetSide() != mozilla::ipc::Side::ParentSide) {
if (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;
switch (actor->GetProtocolId()) {
case ProtocolId::PWebGLMsgStart: {
RefPtr<dom::WebGLParent> webgl = static_cast<dom::WebGLParent*>(actor);
mozilla::ipc::IPCResult rv = webgl->GetFrontBufferSnapshot(&buffer, this);
if (!rv) {
return rv;
}
} break;
case ProtocolId::PWebGPUMsgStart: {
RefPtr<webgpu::WebGPUParent> webgpu =
static_cast<webgpu::WebGPUParent*>(actor);
IntSize size;
mozilla::ipc::IPCResult rv =
webgpu->GetFrontBufferSnapshot(this, aHandle, buffer.shmem, size);
if (!rv) {
return rv;
}
buffer.surfSize.x = static_cast<uint32_t>(size.width);
buffer.surfSize.y = static_cast<uint32_t>(size.height);
} break;
default:
return IPC_FAIL(this, "unsupported protocol");
}
*aResult = std::move(buffer);

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

@ -30,6 +30,7 @@ class CanvasManagerParent final : public PCanvasManagerParent {
mozilla::ipc::IPCResult RecvInitialize(const uint32_t& aId);
mozilla::ipc::IPCResult RecvGetSnapshot(
const uint32_t& aManagerId, const int32_t& aProtocolId,
const CompositableHandle& aHandle,
webgl::FrontBufferSnapshotIpc* aResult);
private:

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

@ -8,6 +8,7 @@
include protocol PWebGL;
include protocol PWebGPU;
using mozilla::layers::CompositableHandle from "mozilla/layers/LayersTypes.h";
using mozilla::webgl::FrontBufferSnapshotIpc from "mozilla/dom/WebGLIpdl.h";
namespace mozilla {
@ -39,7 +40,7 @@ parent:
// 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);
sync GetSnapshot(uint32_t aManagerId, int32_t aProtocolId, CompositableHandle aHandle) returns (FrontBufferSnapshotIpc ret);
};
} // gfx