Bug 1938053 - Accelerate canvas drawImage of a WebGL context. r=aosmond

Accelerated Canvas2D and WebGL both live within the GPU process and run within
the same thread. We want to avoid any kind of readbacks from the GPU process
to the content process when doing a drawImage of a WebGL context to an AC2D
canvas.

To achieve this, we pause the AC2D recording translation with an AwaitTranslationSync
event identified by a sync-id. Then we send a request over IPDL to snapshot the
WebGL context while this pause is ongoing via a SnapshotExternalCanvas IPDL message,
which uses the sync-id to identify the snapshot safely in a table of such external
snapshots and force translation to resume. Finally, we send a ResolveExternalSnapshot
event within the recording stream to lookup the snapshot based on the sync-id and
assign it an alias that can be used within the recording stream playback for drawImage.

The sync-id mechanism acts as a sequenced fence so that multiple SnapshotExternalCanvas
requests can be encountered simultaneously from IPDL without confusing the recording
playback.

Differential Revision: https://phabricator.services.mozilla.com/D243399
This commit is contained in:
Lee Salzman
2025-04-15 16:44:20 +00:00
parent 4ce4821e3a
commit 1d18f4b2f5
19 changed files with 498 additions and 22 deletions

View File

@@ -1142,6 +1142,11 @@ already_AddRefed<gfx::SourceSurface> ClientWebGLContext::GetSurfaceSnapshot(
return ret.forget();
}
mozilla::ipc::IProtocol* ClientWebGLContext::SupportsSnapshotExternalCanvas()
const {
return GetChild();
}
RefPtr<gfx::SourceSurface> ClientWebGLContext::GetFrontBufferSnapshot(
const bool requireAlphaPremult) {
const FuncScope funcScope(*this, "<GetSurfaceSnapshot>");

View File

@@ -1012,6 +1012,8 @@ class ClientWebGLContext final : public nsICanvasRenderingContextInternal,
already_AddRefed<mozilla::gfx::SourceSurface> GetSurfaceSnapshot(
gfxAlphaType* out_alphaType) override;
mozilla::ipc::IProtocol* SupportsSnapshotExternalCanvas() const override;
void SetOpaqueValueFromOpaqueAttr(bool) override {};
bool GetIsOpaque() override { return !mInitialOptions->alpha; }
@@ -1420,6 +1422,8 @@ class ClientWebGLContext final : public nsICanvasRenderingContextInternal,
void Flush(bool flushGl = true) const;
void SyncSnapshot() override { Flush(); }
void Finish();
void FrontFace(GLenum mode);

View File

@@ -1032,9 +1032,10 @@ bool TexUnpackSurface::TexOrSubImage(bool isSubImage, bool needsRespec,
// process as the WebGL canvas. Query it for the surface.
const auto& sdc = sd.get_SurfaceDescriptorCanvasSurface();
uint32_t managerId = sdc.managerId();
int32_t canvasId = sdc.canvasId();
uintptr_t surfaceId = sdc.surfaceId();
surf = gfx::CanvasManagerParent::GetCanvasSurface(webgl->GetContentId(),
managerId, surfaceId);
surf = gfx::CanvasManagerParent::GetCanvasSurface(
webgl->GetContentId(), managerId, canvasId, surfaceId);
if (!surf) {
gfxCriticalNote << "TexUnpackSurface failed to get CanvasSurface";
return false;

View File

@@ -110,6 +110,8 @@ class WebGLParent : public PWebGLParent, public SupportsWeakPtr {
const RefPtr<layers::SharedSurfacesHolder> mSharedSurfacesHolder;
const dom::ContentParentId mContentId;
HostWebGLContext* GetHostWebGLContext() const { return mHost.get(); }
private:
~WebGLParent();

View File

@@ -10,6 +10,7 @@
#include "mozilla/dom/Event.h"
#include "mozilla/dom/WorkerCommon.h"
#include "mozilla/dom/WorkerPrivate.h"
#include "mozilla/gfx/DrawTargetRecording.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/PresShell.h"
#include "nsContentUtils.h"
@@ -140,3 +141,27 @@ bool nsICanvasRenderingContextInternal::DispatchEvent(
}
return useDefaultHandler;
}
already_AddRefed<mozilla::gfx::SourceSurface>
nsICanvasRenderingContextInternal::GetOptimizedSnapshot(
mozilla::gfx::DrawTarget* aTarget, gfxAlphaType* out_alphaType) {
if (aTarget &&
aTarget->GetBackendType() == mozilla::gfx::BackendType::RECORDING) {
if (auto* actor = SupportsSnapshotExternalCanvas()) {
// If this snapshot is for a recording target, then try to avoid reading
// back any data by using SnapshotExternalCanvas instead. This avoids
// having sync interactions between GPU and content process.
if (RefPtr<mozilla::gfx::SourceSurface> surf =
static_cast<mozilla::gfx::DrawTargetRecording*>(aTarget)
->SnapshotExternalCanvas(this, actor)) {
if (out_alphaType) {
*out_alphaType =
GetIsOpaque() ? gfxAlphaType::Opaque : gfxAlphaType::Premult;
}
return surf.forget();
}
}
}
return GetSurfaceSnapshot(out_alphaType);
}

View File

@@ -41,6 +41,9 @@ class nsDisplayListBuilder;
class ClientWebGLContext;
class PresShell;
class WebGLFramebufferJS;
namespace ipc {
class IProtocol;
} // namespace ipc
namespace layers {
class CanvasRenderer;
class CompositableForwarder;
@@ -138,11 +141,14 @@ class nsICanvasRenderingContextInternal : public nsISupports,
// provided DrawTarget, which may be nullptr. By default, this will defer to
// GetSurfaceSnapshot and ignore target-dependent optimization.
virtual already_AddRefed<mozilla::gfx::SourceSurface> GetOptimizedSnapshot(
mozilla::gfx::DrawTarget* aTarget,
gfxAlphaType* out_alphaType = nullptr) {
return GetSurfaceSnapshot(out_alphaType);
mozilla::gfx::DrawTarget* aTarget, gfxAlphaType* out_alphaType = nullptr);
virtual mozilla::ipc::IProtocol* SupportsSnapshotExternalCanvas() const {
return nullptr;
}
virtual void SyncSnapshot() {}
virtual RefPtr<mozilla::gfx::SourceSurface> GetFrontBufferSnapshot(bool) {
return GetSurfaceSnapshot();
}

View File

@@ -23,6 +23,10 @@
#include "nsISupportsImpl.h"
namespace mozilla {
namespace layers {
class CanvasChild;
} // namespace layers
namespace gfx {
class DrawTargetRecording;
@@ -224,6 +228,10 @@ class DrawEventRecorderPrivate : public DrawEventRecorder {
using ExternalImagesHolder = std::deque<ExternalImageEntry>;
virtual already_AddRefed<layers::CanvasChild> GetCanvasChild() const {
return nullptr;
}
protected:
NS_DECL_OWNINGTHREAD

View File

@@ -486,6 +486,24 @@ already_AddRefed<SourceSurface> DrawTargetRecording::Snapshot() {
return retSurf.forget();
}
already_AddRefed<SourceSurface>
DrawTargetRecording::CreateExternalSourceSurface(const IntSize& aSize,
SurfaceFormat aFormat) {
RefPtr<SourceSurface> retSurf =
new SourceSurfaceRecording(aSize, aFormat, mRecorder);
return retSurf.forget();
}
already_AddRefed<SourceSurface> DrawTargetRecording::SnapshotExternalCanvas(
nsICanvasRenderingContextInternal* aCanvas,
mozilla::ipc::IProtocol* aActor) {
if (RefPtr<layers::CanvasChild> canvasChild = mRecorder->GetCanvasChild()) {
return canvasChild->SnapshotExternalCanvas(this, aCanvas, aActor);
}
return nullptr;
}
already_AddRefed<SourceSurface> DrawTargetRecording::IntoLuminanceSource(
LuminanceType aLuminanceType, float aOpacity) {
RefPtr<SourceSurface> retSurf =

View File

@@ -10,8 +10,16 @@
#include "2D.h"
#include "DrawEventRecorder.h"
class nsICanvasRenderingContextInternal;
namespace mozilla {
namespace ipc {
class IProtocol;
} // namespace ipc
namespace layers {
class CanvasChild;
class CanvasDrawEventRecorder;
class RecordedTextureData;
struct RemoteTextureOwnerId;
@@ -392,6 +400,17 @@ class DrawTargetRecording final : public DrawTarget {
layers::RecordedTextureData* mTextureData = nullptr;
friend class layers::CanvasChild;
already_AddRefed<SourceSurface> CreateExternalSourceSurface(
const IntSize& aSize, SurfaceFormat aFormat);
friend class ::nsICanvasRenderingContextInternal;
already_AddRefed<SourceSurface> SnapshotExternalCanvas(
nsICanvasRenderingContextInternal* aCanvas,
mozilla::ipc::IProtocol* aActor);
private:
/**
* Used for creating a DrawTargetRecording for a CreateSimilarDrawTarget call.

View File

@@ -239,20 +239,33 @@ mozilla::ipc::IPCResult CanvasManagerParent::RecvGetSnapshot(
return IPC_OK();
}
/* static */ mozilla::ipc::IProtocol* CanvasManagerParent::GetCanvasActor(
dom::ContentParentId aContentId, uint32_t aManagerId, int32_t aCanvasId) {
IProtocol* actor = nullptr;
for (CanvasManagerParent* i : sManagers) {
if (i->mContentId == aContentId && i->mId == aManagerId) {
actor = i->Lookup(aCanvasId);
break;
}
}
return actor;
}
/* static */ already_AddRefed<DataSourceSurface>
CanvasManagerParent::GetCanvasSurface(dom::ContentParentId aContentId,
uint32_t aManagerId,
uint32_t aManagerId, int32_t aCanvasId,
uintptr_t aSurfaceId) {
for (CanvasManagerParent* manager : sManagers) {
if (manager->mContentId == aContentId && manager->mId == aManagerId) {
for (const auto& canvas : manager->ManagedPCanvasParent()) {
RefPtr<layers::CanvasTranslator> ct =
static_cast<layers::CanvasTranslator*>(canvas);
if (RefPtr<DataSourceSurface> surf = ct->WaitForSurface(aSurfaceId)) {
return surf.forget();
}
}
}
IProtocol* actor = GetCanvasActor(aContentId, aManagerId, aCanvasId);
if (!actor) {
return nullptr;
}
switch (actor->GetProtocolId()) {
case ProtocolId::PCanvasMsgStart:
return static_cast<layers::CanvasTranslator*>(actor)->WaitForSurface(
aSurfaceId);
default:
MOZ_ASSERT_UNREACHABLE("Unsupported protocol");
break;
}
return nullptr;
}

View File

@@ -52,8 +52,11 @@ class CanvasManagerParent final : public PCanvasManagerParent {
const Maybe<RawId>& aCommandEncoderId,
webgl::FrontBufferSnapshotIpc* aResult);
static mozilla::ipc::IProtocol* GetCanvasActor(
dom::ContentParentId aContentId, uint32_t aManagerId, int32_t aCanvasId);
static already_AddRefed<DataSourceSurface> GetCanvasSurface(
dom::ContentParentId aContentId, uint32_t aManagerId,
dom::ContentParentId aContentId, uint32_t aManagerId, int32_t aCanvasId,
uintptr_t aSurfaceId);
private:

View File

@@ -91,6 +91,8 @@ class CanvasDrawEventRecorder final : public gfx::DrawEventRecorderPrivate,
* Causes the reader to resume processing when it is in a stopped state.
*/
virtual bool RestartReader() = 0;
virtual already_AddRefed<layers::CanvasChild> GetCanvasChild() const = 0;
};
bool Init(TextureType aTextureType, TextureType aWebglTextureType,
@@ -137,6 +139,10 @@ class CanvasDrawEventRecorder final : public gfx::DrawEventRecorderPrivate,
void ClearProcessedExternalImages();
already_AddRefed<layers::CanvasChild> GetCanvasChild() const override {
return mHelpers->GetCanvasChild();
}
protected:
gfx::ContiguousBuffer& GetContiguousBuffer(size_t aSize) final;

View File

@@ -47,7 +47,9 @@ const EventType DROP_BUFFER = EventType(EventType::LAST + 16);
const EventType PREPARE_SHMEM = EventType(EventType::LAST + 17);
const EventType PRESENT_TEXTURE = EventType(EventType::LAST + 18);
const EventType DEVICE_RESET_ACKNOWLEDGED = EventType(EventType::LAST + 19);
const EventType LAST_CANVAS_EVENT_TYPE = DEVICE_RESET_ACKNOWLEDGED;
const EventType AWAIT_TRANSLATION_SYNC = EventType(EventType::LAST + 20);
const EventType RESOLVE_EXTERNAL_SNAPSHOT = EventType(EventType::LAST + 21);
const EventType LAST_CANVAS_EVENT_TYPE = RESOLVE_EXTERNAL_SNAPSHOT;
class RecordedCanvasBeginTransaction final
: public RecordedEventDerived<RecordedCanvasBeginTransaction> {
@@ -668,6 +670,87 @@ class RecordedPauseTranslation final
std::string GetName() const final { return "RecordedPauseTranslation"; }
};
class RecordedAwaitTranslationSync final
: public RecordedEventDerived<RecordedAwaitTranslationSync> {
public:
explicit RecordedAwaitTranslationSync(uint64_t aSyncId)
: RecordedEventDerived(AWAIT_TRANSLATION_SYNC), mSyncId(aSyncId) {}
template <class S>
MOZ_IMPLICIT RecordedAwaitTranslationSync(S& aStream);
bool PlayCanvasEvent(CanvasTranslator* aTranslator) const {
aTranslator->AwaitTranslationSync(mSyncId);
return true;
}
template <class S>
void Record(S& aStream) const;
std::string GetName() const final { return "RecordedAwaitTranslationSync"; }
private:
uint64_t mSyncId = 0;
};
template <class S>
void RecordedAwaitTranslationSync::Record(S& aStream) const {
WriteElement(aStream, mSyncId);
}
template <class S>
RecordedAwaitTranslationSync::RecordedAwaitTranslationSync(S& aStream)
: RecordedEventDerived(AWAIT_TRANSLATION_SYNC) {
ReadElement(aStream, mSyncId);
}
class RecordedResolveExternalSnapshot final
: public RecordedEventDerived<RecordedResolveExternalSnapshot> {
public:
explicit RecordedResolveExternalSnapshot(uint64_t aSyncId,
ReferencePtr aRefPtr)
: RecordedEventDerived(RESOLVE_EXTERNAL_SNAPSHOT),
mSyncId(aSyncId),
mRefPtr(aRefPtr) {}
template <class S>
MOZ_IMPLICIT RecordedResolveExternalSnapshot(S& aStream);
bool PlayCanvasEvent(CanvasTranslator* aTranslator) const {
RefPtr<gfx::SourceSurface> snapshot =
aTranslator->LookupExternalSnapshot(mSyncId);
if (!snapshot) {
return false;
}
aTranslator->AddSourceSurface(mRefPtr, snapshot);
return true;
}
template <class S>
void Record(S& aStream) const;
std::string GetName() const final {
return "RecordedResolveExternalSnapshot";
}
private:
uint64_t mSyncId = 0;
ReferencePtr mRefPtr;
};
template <class S>
void RecordedResolveExternalSnapshot::Record(S& aStream) const {
WriteElement(aStream, mSyncId);
WriteElement(aStream, mRefPtr);
}
template <class S>
RecordedResolveExternalSnapshot::RecordedResolveExternalSnapshot(S& aStream)
: RecordedEventDerived(RESOLVE_EXTERNAL_SNAPSHOT) {
ReadElement(aStream, mSyncId);
ReadElement(aStream, mRefPtr);
}
class RecordedRecycleBuffer final
: public RecordedEventDerived<RecordedRecycleBuffer> {
public:
@@ -811,7 +894,9 @@ RecordedPresentTexture::RecordedPresentTexture(S& aStream)
f(DROP_BUFFER, RecordedDropBuffer); \
f(PREPARE_SHMEM, RecordedPrepareShmem); \
f(PRESENT_TEXTURE, RecordedPresentTexture); \
f(DEVICE_RESET_ACKNOWLEDGED, RecordedDeviceResetAcknowledged);
f(DEVICE_RESET_ACKNOWLEDGED, RecordedDeviceResetAcknowledged); \
f(AWAIT_TRANSLATION_SYNC, RecordedAwaitTranslationSync); \
f(RESOLVE_EXTERNAL_SNAPSHOT, RecordedResolveExternalSnapshot);
} // namespace layers
} // namespace mozilla

View File

@@ -13,6 +13,7 @@
#include "mozilla/gfx/CanvasManagerChild.h"
#include "mozilla/gfx/CanvasShutdownManager.h"
#include "mozilla/gfx/DrawTargetRecording.h"
#include "mozilla/gfx/gfxVars.h"
#include "mozilla/gfx/Tools.h"
#include "mozilla/gfx/Rect.h"
#include "mozilla/gfx/Point.h"
@@ -25,7 +26,9 @@
#include "mozilla/AppShutdown.h"
#include "mozilla/Maybe.h"
#include "mozilla/Mutex.h"
#include "mozilla/StaticPrefs_gfx.h"
#include "nsIObserverService.h"
#include "nsICanvasRenderingContextInternal.h"
#include "RecordedCanvasEventImpl.h"
namespace mozilla {
@@ -81,6 +84,11 @@ class RecorderHelpers final : public CanvasDrawEventRecorder::Helpers {
return mCanvasChild->SendRestartTranslation();
}
already_AddRefed<CanvasChild> GetCanvasChild() const override {
RefPtr<CanvasChild> canvasChild(mCanvasChild);
return canvasChild.forget();
}
private:
const WeakPtr<CanvasChild> mCanvasChild;
};
@@ -157,7 +165,7 @@ class SourceSurfaceCanvasRecording final : public gfx::SourceSurface {
bool GetSurfaceDescriptor(SurfaceDescriptor& aDesc) const final {
aDesc = SurfaceDescriptorCanvasSurface(
static_cast<gfx::CanvasManagerChild*>(mCanvasChild->Manager())->Id(),
uintptr_t(gfx::ReferencePtr(this)));
mCanvasChild->Id(), uintptr_t(gfx::ReferencePtr(this)));
return true;
}
@@ -719,5 +727,51 @@ ipc::IPCResult CanvasChild::RecvNotifyTextureDestruction(
return IPC_OK();
}
already_AddRefed<gfx::SourceSurface> CanvasChild::SnapshotExternalCanvas(
gfx::DrawTargetRecording* aTarget,
nsICanvasRenderingContextInternal* aCanvas,
mozilla::ipc::IProtocol* aActor) {
// SnapshotExternalCanvas is only valid to use if using Accelerated Canvas2D
// with the pending events queue enabled. This ensures WebGL and AC2D are
// running under the same thread, and that events can be paused or resumed
// while synchronizing between WebGL and AC2D.
if (!gfx::gfxVars::UseAcceleratedCanvas2D() ||
!StaticPrefs::gfx_canvas_remote_use_canvas_translator_event_AtStartup()) {
return nullptr;
}
gfx::SurfaceFormat format = aCanvas->GetIsOpaque()
? gfx::SurfaceFormat::B8G8R8X8
: gfx::SurfaceFormat::B8G8R8A8;
gfx::IntSize size(aCanvas->GetWidth(), aCanvas->GetHeight());
// Create a source sourface that will be associated with the snapshot.
RefPtr<gfx::SourceSurface> surface =
aTarget->CreateExternalSourceSurface(size, format);
if (!surface) {
return nullptr;
}
// Pause translation until the sync-id identifying the snapshot is received.
uint64_t syncId = ++mLastSyncId;
mRecorder->RecordEvent(RecordedAwaitTranslationSync(syncId));
// Flush WebGL to cause any IPDL messages to get sent at this sync point.
aCanvas->SyncSnapshot();
// Once the IPDL message is sent to generate the snapshot, resolve the sync-id
// to a surface in the recording stream. The AwaitTranslationSync above will
// ensure this event is not translated until the snapshot is generated first.
mRecorder->RecordEvent(
RecordedResolveExternalSnapshot(syncId, gfx::ReferencePtr(surface)));
uint32_t managerId = static_cast<gfx::CanvasManagerChild*>(Manager())->Id();
int32_t canvasId = aActor->Id();
// Actually send the request via IPDL to snapshot the external WebGL canvas.
SendSnapshotExternalCanvas(syncId, managerId, canvasId);
return surface.forget();
}
} // namespace layers
} // namespace mozilla

View File

@@ -14,6 +14,8 @@
#include "mozilla/layers/SourceSurfaceSharedData.h"
#include "mozilla/WeakPtr.h"
class nsICanvasRenderingContextInternal;
namespace mozilla {
namespace dom {
@@ -166,6 +168,11 @@ class CanvasChild final : public PCanvasChild, public SupportsWeakPtr {
void ReturnDataSurfaceShmem(
std::shared_ptr<ipc::ReadOnlySharedMemoryMapping>&& aDataSurfaceShmem);
already_AddRefed<gfx::SourceSurface> SnapshotExternalCanvas(
gfx::DrawTargetRecording* aTarget,
nsICanvasRenderingContextInternal* aCanvas,
mozilla::ipc::IProtocol* aActor);
protected:
void ActorDestroy(ActorDestroyReason aWhy) final;
@@ -201,6 +208,7 @@ class CanvasChild final : public PCanvasChild, public SupportsWeakPtr {
bool mIsInTransaction = false;
bool mDormant = false;
bool mBlocked = false;
uint64_t mLastSyncId = 0;
};
} // namespace layers

View File

@@ -17,6 +17,7 @@
#include "mozilla/gfx/GPUParent.h"
#include "mozilla/gfx/GPUProcessManager.h"
#include "mozilla/gfx/Logging.h"
#include "mozilla/gfx/Swizzle.h"
#include "mozilla/ipc/Endpoint.h"
#include "mozilla/ipc/SharedMemoryHandle.h"
#include "mozilla/layers/BufferTexture.h"
@@ -29,6 +30,8 @@
#include "mozilla/SyncRunnable.h"
#include "mozilla/TaskQueue.h"
#include "GLContext.h"
#include "HostWebGLContext.h"
#include "WebGLParent.h"
#include "RecordedCanvasEventImpl.h"
#if defined(XP_WIN)
@@ -697,7 +700,7 @@ bool CanvasTranslator::TranslateRecording() {
mHeader->processedCount++;
if (mHeader->readerState == State::Paused) {
if (mHeader->readerState == State::Paused || PauseUntilSync()) {
// We're waiting for an IPDL message return false, because we will resume
// translation after it is received.
Flush();
@@ -760,7 +763,7 @@ void CanvasTranslator::HandleCanvasTranslatorEvents() {
{
MutexAutoLock lock(mCanvasTranslatorEventsLock);
MOZ_ASSERT_IF(mIPDLClosed, mPendingCanvasTranslatorEvents.empty());
if (mPendingCanvasTranslatorEvents.empty()) {
if (mPendingCanvasTranslatorEvents.empty() || PauseUntilSync()) {
mCanvasTranslatorEventsRunnable = nullptr;
return;
}
@@ -800,6 +803,12 @@ void CanvasTranslator::HandleCanvasTranslatorEvents() {
if (mIPDLClosed) {
return;
}
if (PauseUntilSync()) {
mCanvasTranslatorEventsRunnable = nullptr;
mPendingCanvasTranslatorEvents.push_front(
CanvasTranslatorEvent::TranslateRecording());
return;
}
if (!mIPDLClosed && !dispatchTranslate &&
!mPendingCanvasTranslatorEvents.empty()) {
auto& front = mPendingCanvasTranslatorEvents.front();
@@ -1621,6 +1630,175 @@ void CanvasTranslator::PauseTranslation() {
mHeader->readerState = State::Paused;
}
void CanvasTranslator::AwaitTranslationSync(uint64_t aSyncId) {
if (NS_WARN_IF(!UsePendingCanvasTranslatorEvents()) ||
NS_WARN_IF(!IsInTaskQueue()) || NS_WARN_IF(mAwaitSyncId >= aSyncId)) {
return;
}
mAwaitSyncId = aSyncId;
}
void CanvasTranslator::SyncTranslation(uint64_t aSyncId) {
if (NS_WARN_IF(!IsInTaskQueue()) || NS_WARN_IF(aSyncId <= mLastSyncId)) {
return;
}
bool wasPaused = PauseUntilSync();
mLastSyncId = aSyncId;
// If translation was previously paused waiting on a sync-id, check if sync-id
// encountered requires restarting translation.
if (wasPaused && !PauseUntilSync()) {
HandleCanvasTranslatorEvents();
}
}
class WebGLContextBackBufferAccess : public WebGLContext {
public:
already_AddRefed<gfx::SourceSurface> GetBackBufferSnapshot(
const bool requireAlphaPremult);
};
already_AddRefed<gfx::SourceSurface>
WebGLContextBackBufferAccess::GetBackBufferSnapshot(
const bool requireAlphaPremult) {
if (IsContextLost()) {
return nullptr;
}
const auto surfSize = DrawingBufferSize();
if (surfSize.x <= 0 || surfSize.y <= 0) {
return nullptr;
}
const auto& options = Options();
const auto surfFormat = options.alpha ? gfx::SurfaceFormat::B8G8R8A8
: gfx::SurfaceFormat::B8G8R8X8;
RefPtr<gfx::DataSourceSurface> dataSurf =
gfx::Factory::CreateDataSourceSurface(
gfx::IntSize(surfSize.x, surfSize.y), surfFormat);
if (!dataSurf) {
NS_WARNING("Failed to alloc DataSourceSurface for GetBackBufferSnapshot");
return nullptr;
}
{
gfx::DataSourceSurface::ScopedMap map(dataSurf,
gfx::DataSourceSurface::READ_WRITE);
if (!map.IsMapped()) {
NS_WARNING("Failed to map DataSourceSurface for GetBackBufferSnapshot");
return nullptr;
}
// GetDefaultFBForRead might overwrite FB state if it needs to resolve a
// multisampled FB, so save/restore the FB state here just in case.
const gl::ScopedBindFramebuffer bindFb(GL());
const auto fb = GetDefaultFBForRead();
if (!fb) {
gfxCriticalNote << "GetDefaultFBForRead failed for GetBackBufferSnapshot";
return nullptr;
}
const auto byteCount = CheckedInt<size_t>(map.GetStride()) * surfSize.y;
if (!byteCount.isValid()) {
gfxCriticalNote << "Invalid byte count for GetBackBufferSnapshot";
return nullptr;
}
const Range<uint8_t> range = {map.GetData(), byteCount.value()};
if (!SnapshotInto(fb->mFB, fb->mSize, range,
Some(size_t(map.GetStride())))) {
gfxCriticalNote << "SnapshotInto failed for GetBackBufferSnapshot";
return nullptr;
}
if (requireAlphaPremult && options.alpha && !options.premultipliedAlpha) {
bool rv = gfx::PremultiplyYFlipData(
map.GetData(), map.GetStride(), gfx::SurfaceFormat::R8G8B8A8,
map.GetData(), map.GetStride(), surfFormat, dataSurf->GetSize());
MOZ_RELEASE_ASSERT(rv, "PremultiplyYFlipData failed!");
} else {
bool rv = gfx::SwizzleYFlipData(
map.GetData(), map.GetStride(), gfx::SurfaceFormat::R8G8B8A8,
map.GetData(), map.GetStride(), surfFormat, dataSurf->GetSize());
MOZ_RELEASE_ASSERT(rv, "SwizzleYFlipData failed!");
}
}
return dataSurf.forget();
}
mozilla::ipc::IPCResult CanvasTranslator::RecvSnapshotExternalCanvas(
uint64_t aSyncId, uint32_t aManagerId, int32_t aCanvasId) {
if (NS_WARN_IF(!IsInTaskQueue())) {
return IPC_FAIL(this,
"RecvSnapshotExternalCanvas used outside of task queue.");
}
// Verify that snapshot requests are not received out of order order.
if (NS_WARN_IF(aSyncId <= mLastSyncId)) {
return IPC_FAIL(this, "RecvSnapShotExternalCanvas received too late.");
}
// Attempt to snapshot an external canvas that is associated with the same
// content process as this canvas. On success, associate it with the sync-id.
RefPtr<gfx::SourceSurface> surf;
if (auto* actor = gfx::CanvasManagerParent::GetCanvasActor(
mContentId, aManagerId, aCanvasId)) {
switch (actor->GetProtocolId()) {
case ProtocolId::PWebGLMsgStart:
if (auto* hostContext =
static_cast<dom::WebGLParent*>(actor)->GetHostWebGLContext()) {
surf = static_cast<WebGLContextBackBufferAccess*>(
hostContext->GetWebGLContext())
->GetBackBufferSnapshot(true);
}
break;
default:
MOZ_ASSERT_UNREACHABLE("Unsupported protocol");
break;
}
}
if (surf) {
mExternalSnapshots.InsertOrUpdate(aSyncId, surf);
}
// Regardless, sync translation so it may resume after attempting snapshot.
SyncTranslation(aSyncId);
if (!surf) {
return IPC_FAIL(this, "SnapshotExternalCanvas failed to get surface.");
}
return IPC_OK();
}
already_AddRefed<gfx::SourceSurface> CanvasTranslator::LookupExternalSnapshot(
uint64_t aSyncId) {
MOZ_ASSERT(IsInTaskQueue());
uint64_t prevSyncId = mLastSyncId;
if (NS_WARN_IF(aSyncId > mLastSyncId)) {
// If arriving here, a previous SnapshotExternalCanvas IPDL message never
// arrived for some reason. Sync translation here to avoid locking up.
SyncTranslation(aSyncId);
}
RefPtr<gfx::SourceSurface> surf;
// Check if the snapshot was added. This should only ever be called once per
// snapshot, as it is removed from the table when resolved.
if (mExternalSnapshots.Remove(aSyncId, getter_AddRefs(surf))) {
return surf.forget();
}
// There was no snapshot available, which can happen if this was called
// before or without a corresponding SnapshotExternalCanvas, or if called
// multiple times.
if (aSyncId > prevSyncId) {
gfxCriticalNoteOnce << "External canvas snapshot resolved before creation.";
} else {
gfxCriticalNoteOnce << "Exernal canvas snapshot already resolved.";
}
return nullptr;
}
already_AddRefed<gfx::GradientStops> CanvasTranslator::GetOrCreateGradientStops(
gfx::DrawTarget* aDrawTarget, gfx::GradientStop* aRawStops,
uint32_t aNumStops, gfx::ExtendMode aExtendMode) {

View File

@@ -181,6 +181,29 @@ class CanvasTranslator final : public gfx::InlineTranslator,
void PauseTranslation();
/**
* Wait for a given sync-id to be encountered before resume translation.
*/
void AwaitTranslationSync(uint64_t aSyncId);
/**
* Signal that translation should resume if waiting on the given sync-id.
*/
void SyncTranslation(uint64_t aSyncId);
/**
* Snapshot an external canvas and label it for later lookup under a sync-id.
*/
mozilla::ipc::IPCResult RecvSnapshotExternalCanvas(uint64_t aSyncId,
uint32_t aManagerId,
int32_t aCanvasId);
/**
* Resolves the given sync-id from the recording stream to a snapshot from
* an external canvas that was received from an IPDL message.
*/
already_AddRefed<gfx::SourceSurface> LookupExternalSnapshot(uint64_t aSyncId);
/**
* Removes the texture and other objects associated with a texture ID.
*
@@ -482,6 +505,18 @@ class CanvasTranslator final : public gfx::InlineTranslator,
// so long as the checkpoint has not yet been reached.
int64_t mFlushCheckpoint = 0;
// The sync-id that the translator is awaiting and must be encountered before
// it is ready to resume translation.
uint64_t mAwaitSyncId = 0;
// The last sync-id that was actually encountered.
uint64_t mLastSyncId = 0;
// A table of external canvas snapshots associated with a given sync-id.
nsRefPtrHashtable<nsUint64HashKey, gfx::SourceSurface> mExternalSnapshots;
// Signal that translation should pause because it is still awaiting a sync-id
// that has not been encountered yet.
bool PauseUntilSync() const { return mAwaitSyncId > mLastSyncId; }
struct CanvasShmem {
ipc::ReadOnlySharedMemoryMapping shmem;
bool IsValid() const { return shmem.IsValid(); }

View File

@@ -206,6 +206,7 @@ struct SurfaceDescriptorShared
[Comparable] struct SurfaceDescriptorCanvasSurface {
uint32_t managerId;
int32_t canvasId;
uintptr_t surfaceId;
};

View File

@@ -66,6 +66,11 @@ parent:
*/
async DropFreeBuffersWhenDormant();
/**
* Snapshot an external canvas and label it for later lookup under a sync-id.
*/
async SnapshotExternalCanvas(uint64_t aSyncId, uint32_t aManagerId, int32_t aCanvasId);
async __delete__();
child: