summaryrefslogtreecommitdiffstats
path: root/gfx/layers/ipc/CanvasTranslator.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/layers/ipc/CanvasTranslator.cpp')
-rw-r--r--gfx/layers/ipc/CanvasTranslator.cpp1201
1 files changed, 1201 insertions, 0 deletions
diff --git a/gfx/layers/ipc/CanvasTranslator.cpp b/gfx/layers/ipc/CanvasTranslator.cpp
new file mode 100644
index 0000000000..08150d6952
--- /dev/null
+++ b/gfx/layers/ipc/CanvasTranslator.cpp
@@ -0,0 +1,1201 @@
+/* -*- 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 https://mozilla.org/MPL/2.0/. */
+
+#include "CanvasTranslator.h"
+
+#include "gfxGradientCache.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/CanvasManagerParent.h"
+#include "mozilla/gfx/CanvasRenderThread.h"
+#include "mozilla/gfx/DrawTargetWebgl.h"
+#include "mozilla/gfx/gfxVars.h"
+#include "mozilla/gfx/GPUParent.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/ipc/Endpoint.h"
+#include "mozilla/layers/BufferTexture.h"
+#include "mozilla/layers/CanvasTranslator.h"
+#include "mozilla/layers/ImageDataSerializer.h"
+#include "mozilla/layers/SharedSurfacesParent.h"
+#include "mozilla/layers/TextureClient.h"
+#include "mozilla/StaticPrefs_gfx.h"
+#include "mozilla/SyncRunnable.h"
+#include "mozilla/TaskQueue.h"
+#include "mozilla/Telemetry.h"
+#include "GLContext.h"
+#include "RecordedCanvasEventImpl.h"
+
+#if defined(XP_WIN)
+# include "mozilla/gfx/DeviceManagerDx.h"
+# include "mozilla/layers/TextureD3D11.h"
+#endif
+
+namespace mozilla {
+namespace layers {
+
+UniquePtr<TextureData> CanvasTranslator::CreateTextureData(
+ const gfx::IntSize& aSize, gfx::SurfaceFormat aFormat, bool aClear) {
+ TextureData* textureData = nullptr;
+ TextureAllocationFlags allocFlags =
+ aClear ? ALLOC_CLEAR_BUFFER : ALLOC_DEFAULT;
+ switch (mTextureType) {
+#ifdef XP_WIN
+ case TextureType::D3D11: {
+ textureData =
+ D3D11TextureData::Create(aSize, aFormat, allocFlags, mDevice);
+ break;
+ }
+#endif
+ case TextureType::Unknown:
+ textureData = BufferTextureData::Create(
+ aSize, aFormat, gfx::BackendType::SKIA, LayersBackend::LAYERS_WR,
+ TextureFlags::DEALLOCATE_CLIENT | TextureFlags::REMOTE_TEXTURE,
+ allocFlags, nullptr);
+ break;
+ default:
+ textureData = TextureData::Create(mTextureType, aFormat, aSize,
+ allocFlags, mBackendType);
+ break;
+ }
+
+ return WrapUnique(textureData);
+}
+
+CanvasTranslator::CanvasTranslator(
+ layers::SharedSurfacesHolder* aSharedSurfacesHolder,
+ const dom::ContentParentId& aContentId, uint32_t aManagerId)
+ : mTranslationTaskQueue(gfx::CanvasRenderThread::CreateWorkerTaskQueue()),
+ mSharedSurfacesHolder(aSharedSurfacesHolder),
+ mMaxSpinCount(StaticPrefs::gfx_canvas_remote_max_spin_count()),
+ mContentId(aContentId),
+ mManagerId(aManagerId) {
+ mNextEventTimeout = TimeDuration::FromMilliseconds(
+ StaticPrefs::gfx_canvas_remote_event_timeout_ms());
+
+ // Track when remote canvas has been activated.
+ Telemetry::ScalarAdd(Telemetry::ScalarID::GFX_CANVAS_REMOTE_ACTIVATED, 1);
+}
+
+CanvasTranslator::~CanvasTranslator() = default;
+
+void CanvasTranslator::DispatchToTaskQueue(
+ already_AddRefed<nsIRunnable> aRunnable) {
+ if (mTranslationTaskQueue) {
+ MOZ_ALWAYS_SUCCEEDS(mTranslationTaskQueue->Dispatch(std::move(aRunnable)));
+ } else {
+ gfx::CanvasRenderThread::Dispatch(std::move(aRunnable));
+ }
+}
+
+bool CanvasTranslator::IsInTaskQueue() const {
+ if (mTranslationTaskQueue) {
+ return mTranslationTaskQueue->IsCurrentThreadIn();
+ }
+ return gfx::CanvasRenderThread::IsInCanvasRenderThread();
+}
+
+static bool CreateAndMapShmem(RefPtr<ipc::SharedMemoryBasic>& aShmem,
+ Handle&& aHandle,
+ ipc::SharedMemory::OpenRights aOpenRights,
+ size_t aSize) {
+ auto shmem = MakeRefPtr<ipc::SharedMemoryBasic>();
+ if (!shmem->SetHandle(std::move(aHandle), aOpenRights) ||
+ !shmem->Map(aSize)) {
+ return false;
+ }
+
+ shmem->CloseHandle();
+ aShmem = shmem.forget();
+ return true;
+}
+
+bool CanvasTranslator::EnsureSharedContextWebgl() {
+ if (!mSharedContext || mSharedContext->IsContextLost()) {
+ if (mSharedContext) {
+ ForceDrawTargetWebglFallback();
+ if (mRemoteTextureOwner) {
+ // Ensure any shared surfaces referring to the old context go away.
+ mRemoteTextureOwner->ClearRecycledTextures();
+ }
+ }
+ mSharedContext = gfx::SharedContextWebgl::Create();
+ if (!mSharedContext || mSharedContext->IsContextLost()) {
+ mSharedContext = nullptr;
+ BlockCanvas();
+ return false;
+ }
+ }
+ return true;
+}
+
+mozilla::ipc::IPCResult CanvasTranslator::RecvInitTranslator(
+ TextureType aTextureType, gfx::BackendType aBackendType,
+ Handle&& aReadHandle, nsTArray<Handle>&& aBufferHandles,
+ uint64_t aBufferSize, CrossProcessSemaphoreHandle&& aReaderSem,
+ CrossProcessSemaphoreHandle&& aWriterSem) {
+ if (mHeaderShmem) {
+ return IPC_FAIL(this, "RecvInitTranslator called twice.");
+ }
+
+ mTextureType = aTextureType;
+ mBackendType = aBackendType;
+ mOtherPid = OtherPid();
+
+ mHeaderShmem = MakeAndAddRef<ipc::SharedMemoryBasic>();
+ if (!CreateAndMapShmem(mHeaderShmem, std::move(aReadHandle),
+ ipc::SharedMemory::RightsReadWrite, sizeof(Header))) {
+ Deactivate();
+ return IPC_FAIL(this, "Failed to map canvas header shared memory.");
+ }
+
+ mHeader = static_cast<Header*>(mHeaderShmem->memory());
+
+ mWriterSemaphore.reset(CrossProcessSemaphore::Create(std::move(aWriterSem)));
+ mWriterSemaphore->CloseHandle();
+
+ mReaderSemaphore.reset(CrossProcessSemaphore::Create(std::move(aReaderSem)));
+ mReaderSemaphore->CloseHandle();
+
+ if (!CheckForFreshCanvasDevice(__LINE__)) {
+ gfxCriticalNote << "GFX: CanvasTranslator failed to get device";
+ return IPC_OK();
+ }
+
+ if (gfx::gfxVars::UseAcceleratedCanvas2D() && !EnsureSharedContextWebgl()) {
+ gfxCriticalNote
+ << "GFX: CanvasTranslator failed creating WebGL shared context";
+ }
+
+ // Use the first buffer as our current buffer.
+ mDefaultBufferSize = aBufferSize;
+ auto handleIter = aBufferHandles.begin();
+ if (!CreateAndMapShmem(mCurrentShmem.shmem, std::move(*handleIter),
+ ipc::SharedMemory::RightsReadOnly, aBufferSize)) {
+ Deactivate();
+ return IPC_FAIL(this, "Failed to map canvas buffer shared memory.");
+ }
+ mCurrentMemReader = mCurrentShmem.CreateMemReader();
+
+ // Add all other buffers to our recycled CanvasShmems.
+ for (handleIter++; handleIter < aBufferHandles.end(); handleIter++) {
+ CanvasShmem newShmem;
+ if (!CreateAndMapShmem(newShmem.shmem, std::move(*handleIter),
+ ipc::SharedMemory::RightsReadOnly, aBufferSize)) {
+ Deactivate();
+ return IPC_FAIL(this, "Failed to map canvas buffer shared memory.");
+ }
+ mCanvasShmems.emplace(std::move(newShmem));
+ }
+
+ DispatchToTaskQueue(NewRunnableMethod("CanvasTranslator::TranslateRecording",
+ this,
+ &CanvasTranslator::TranslateRecording));
+ return IPC_OK();
+}
+
+ipc::IPCResult CanvasTranslator::RecvRestartTranslation() {
+ if (mDeactivated) {
+ // The other side might have sent a message before we deactivated.
+ return IPC_OK();
+ }
+
+ DispatchToTaskQueue(NewRunnableMethod("CanvasTranslator::TranslateRecording",
+ this,
+ &CanvasTranslator::TranslateRecording));
+
+ return IPC_OK();
+}
+
+ipc::IPCResult CanvasTranslator::RecvAddBuffer(
+ ipc::SharedMemoryBasic::Handle&& aBufferHandle, uint64_t aBufferSize) {
+ if (mDeactivated) {
+ // The other side might have sent a resume message before we deactivated.
+ return IPC_OK();
+ }
+
+ DispatchToTaskQueue(
+ NewRunnableMethod<ipc::SharedMemoryBasic::Handle&&, size_t>(
+ "CanvasTranslator::AddBuffer", this, &CanvasTranslator::AddBuffer,
+ std::move(aBufferHandle), aBufferSize));
+
+ return IPC_OK();
+}
+
+void CanvasTranslator::AddBuffer(ipc::SharedMemoryBasic::Handle&& aBufferHandle,
+ size_t aBufferSize) {
+ MOZ_ASSERT(IsInTaskQueue());
+ if (mHeader->readerState == State::Failed) {
+ // We failed before we got to the pause event.
+ return;
+ }
+
+ if (mHeader->readerState != State::Paused) {
+ gfxCriticalNote << "CanvasTranslator::AddBuffer bad state "
+ << uint32_t(State(mHeader->readerState));
+ MOZ_DIAGNOSTIC_ASSERT(false, "mHeader->readerState == State::Paused");
+ Deactivate();
+ return;
+ }
+
+ MOZ_ASSERT(mDefaultBufferSize != 0);
+
+ // Check and signal the writer when we finish with a buffer, because it
+ // might have hit the buffer count limit and be waiting to use our old one.
+ CheckAndSignalWriter();
+
+ // Default sized buffers will have been queued for recycling.
+ if (mCurrentShmem.Size() == mDefaultBufferSize) {
+ mCanvasShmems.emplace(std::move(mCurrentShmem));
+ }
+
+ CanvasShmem newShmem;
+ if (!CreateAndMapShmem(newShmem.shmem, std::move(aBufferHandle),
+ ipc::SharedMemory::RightsReadOnly, aBufferSize)) {
+ return;
+ }
+
+ mCurrentShmem = std::move(newShmem);
+ mCurrentMemReader = mCurrentShmem.CreateMemReader();
+
+ TranslateRecording();
+}
+
+ipc::IPCResult CanvasTranslator::RecvSetDataSurfaceBuffer(
+ ipc::SharedMemoryBasic::Handle&& aBufferHandle, uint64_t aBufferSize) {
+ if (mDeactivated) {
+ // The other side might have sent a resume message before we deactivated.
+ return IPC_OK();
+ }
+
+ DispatchToTaskQueue(
+ NewRunnableMethod<ipc::SharedMemoryBasic::Handle&&, size_t>(
+ "CanvasTranslator::SetDataSurfaceBuffer", this,
+ &CanvasTranslator::SetDataSurfaceBuffer, std::move(aBufferHandle),
+ aBufferSize));
+
+ return IPC_OK();
+}
+
+void CanvasTranslator::SetDataSurfaceBuffer(
+ ipc::SharedMemoryBasic::Handle&& aBufferHandle, size_t aBufferSize) {
+ MOZ_ASSERT(IsInTaskQueue());
+ if (mHeader->readerState == State::Failed) {
+ // We failed before we got to the pause event.
+ return;
+ }
+
+ if (mHeader->readerState != State::Paused) {
+ gfxCriticalNote << "CanvasTranslator::SetDataSurfaceBuffer bad state "
+ << uint32_t(State(mHeader->readerState));
+ MOZ_DIAGNOSTIC_ASSERT(false, "mHeader->readerState == State::Paused");
+ Deactivate();
+ return;
+ }
+
+ if (!CreateAndMapShmem(mDataSurfaceShmem, std::move(aBufferHandle),
+ ipc::SharedMemory::RightsReadWrite, aBufferSize)) {
+ return;
+ }
+
+ TranslateRecording();
+}
+
+void CanvasTranslator::GetDataSurface(uint64_t aSurfaceRef) {
+ MOZ_ASSERT(IsInTaskQueue());
+
+ ReferencePtr surfaceRef = reinterpret_cast<void*>(aSurfaceRef);
+ gfx::SourceSurface* surface = LookupSourceSurface(surfaceRef);
+ if (!surface) {
+ return;
+ }
+
+ UniquePtr<gfx::DataSourceSurface::ScopedMap> map = GetPreparedMap(surfaceRef);
+ if (!map) {
+ return;
+ }
+
+ auto dstSize = surface->GetSize();
+ auto srcSize = map->GetSurface()->GetSize();
+ gfx::SurfaceFormat format = surface->GetFormat();
+ int32_t bpp = BytesPerPixel(format);
+ int32_t dataFormatWidth = dstSize.width * bpp;
+ int32_t srcStride = map->GetStride();
+ if (dataFormatWidth > srcStride || srcSize != dstSize) {
+ return;
+ }
+
+ int32_t dstStride =
+ ImageDataSerializer::ComputeRGBStride(format, dstSize.width);
+ auto requiredSize =
+ ImageDataSerializer::ComputeRGBBufferSize(dstSize, format);
+ if (requiredSize <= 0 || size_t(requiredSize) > mDataSurfaceShmem->Size()) {
+ return;
+ }
+
+ uint8_t* dst = static_cast<uint8_t*>(mDataSurfaceShmem->memory());
+ const uint8_t* src = map->GetData();
+ const uint8_t* endSrc = src + (srcSize.height * srcStride);
+ while (src < endSrc) {
+ memcpy(dst, src, dataFormatWidth);
+ src += srcStride;
+ dst += dstStride;
+ }
+}
+
+void CanvasTranslator::RecycleBuffer() {
+ mCanvasShmems.emplace(std::move(mCurrentShmem));
+ NextBuffer();
+}
+
+void CanvasTranslator::NextBuffer() {
+ // Check and signal the writer when we finish with a buffer, because it
+ // might have hit the buffer count limit and be waiting to use our old one.
+ CheckAndSignalWriter();
+
+ mCurrentShmem = std::move(mCanvasShmems.front());
+ mCanvasShmems.pop();
+ mCurrentMemReader = mCurrentShmem.CreateMemReader();
+}
+
+void CanvasTranslator::ActorDestroy(ActorDestroyReason why) {
+ MOZ_ASSERT(gfx::CanvasRenderThread::IsInCanvasRenderThread());
+
+ // Since we might need to access the actor status off the owning IPDL thread,
+ // we need to cache it here.
+ mIPDLClosed = true;
+
+ DispatchToTaskQueue(NewRunnableMethod("CanvasTranslator::ClearTextureInfo",
+ this,
+ &CanvasTranslator::ClearTextureInfo));
+
+ if (mTranslationTaskQueue) {
+ gfx::CanvasRenderThread::ShutdownWorkerTaskQueue(mTranslationTaskQueue);
+ return;
+ }
+}
+
+bool CanvasTranslator::CheckDeactivated() {
+ if (mDeactivated) {
+ return true;
+ }
+
+ if (NS_WARN_IF(!gfx::gfxVars::RemoteCanvasEnabled() &&
+ !gfx::gfxVars::UseAcceleratedCanvas2D())) {
+ Deactivate();
+ }
+
+ return mDeactivated;
+}
+
+void CanvasTranslator::Deactivate() {
+ if (mDeactivated) {
+ return;
+ }
+ mDeactivated = true;
+ if (mHeader) {
+ mHeader->readerState = State::Failed;
+ }
+
+ // We need to tell the other side to deactivate. Make sure the stream is
+ // marked as bad so that the writing side won't wait for space to write.
+ gfx::CanvasRenderThread::Dispatch(
+ NewRunnableMethod("CanvasTranslator::SendDeactivate", this,
+ &CanvasTranslator::SendDeactivate));
+
+ // Disable remote canvas for all.
+ gfx::CanvasManagerParent::DisableRemoteCanvas();
+}
+
+inline gfx::DrawTargetWebgl* CanvasTranslator::TextureInfo::GetDrawTargetWebgl(
+ bool aCheckForFallback) const {
+ if ((!mTextureData || !aCheckForFallback) && mDrawTarget &&
+ mDrawTarget->GetBackendType() == gfx::BackendType::WEBGL) {
+ return static_cast<gfx::DrawTargetWebgl*>(mDrawTarget.get());
+ }
+ return nullptr;
+}
+
+bool CanvasTranslator::TryDrawTargetWebglFallback(
+ int64_t aTextureId, gfx::DrawTargetWebgl* aWebgl) {
+ NotifyRequiresRefresh(aTextureId);
+
+ // An existing data snapshot is required for fallback, as we have to avoid
+ // trying to touch the WebGL context, which is assumed to be invalid and not
+ // suitable for readback.
+ if (!aWebgl->HasDataSnapshot()) {
+ return false;
+ }
+
+ const auto& info = mTextureInfo[aTextureId];
+ if (RefPtr<gfx::DrawTarget> dt = CreateFallbackDrawTarget(
+ info.mRefPtr, aTextureId, info.mRemoteTextureOwnerId,
+ aWebgl->GetSize(), aWebgl->GetFormat())) {
+ aWebgl->CopyToFallback(dt);
+ AddDrawTarget(info.mRefPtr, dt);
+ return true;
+ }
+ return false;
+}
+
+void CanvasTranslator::ForceDrawTargetWebglFallback() {
+ // This looks for any DrawTargetWebgls that have a cached data snapshot that
+ // can be used to recover a fallback TextureData in the event of a context
+ // loss.
+ RemoteTextureOwnerIdSet lost;
+ for (const auto& entry : mTextureInfo) {
+ const auto& info = entry.second;
+ if (gfx::DrawTargetWebgl* webgl = info.GetDrawTargetWebgl()) {
+ if (!TryDrawTargetWebglFallback(entry.first, webgl)) {
+ // No fallback could be created, so we need to notify the compositor the
+ // texture won't be pushed.
+ if (mRemoteTextureOwner &&
+ mRemoteTextureOwner->IsRegistered(info.mRemoteTextureOwnerId)) {
+ lost.insert(info.mRemoteTextureOwnerId);
+ }
+ }
+ }
+ }
+ if (!lost.empty()) {
+ mRemoteTextureOwner->NotifyContextLost(&lost);
+ }
+}
+
+void CanvasTranslator::BlockCanvas() {
+ if (mDeactivated || mBlocked) {
+ return;
+ }
+ mBlocked = true;
+ gfx::CanvasRenderThread::Dispatch(
+ NewRunnableMethod("CanvasTranslator::SendBlockCanvas", this,
+ &CanvasTranslator::SendBlockCanvas));
+}
+
+void CanvasTranslator::CheckAndSignalWriter() {
+ do {
+ switch (mHeader->writerState) {
+ case State::Processing:
+ case State::Failed:
+ return;
+ case State::AboutToWait:
+ // The writer is making a decision about whether to wait. So, we must
+ // wait until it has decided to avoid races. Check if the writer is
+ // closed to avoid hangs.
+ if (mIPDLClosed) {
+ return;
+ }
+ continue;
+ case State::Waiting:
+ if (mHeader->processedCount >= mHeader->writerWaitCount) {
+ mHeader->writerState = State::Processing;
+ mWriterSemaphore->Signal();
+ }
+ return;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Invalid waiting state.");
+ return;
+ }
+ } while (true);
+}
+
+bool CanvasTranslator::HasPendingEvent() {
+ return mHeader->processedCount < mHeader->eventCount;
+}
+
+bool CanvasTranslator::ReadPendingEvent(EventType& aEventType) {
+ ReadElementConstrained(mCurrentMemReader, aEventType,
+ EventType::DRAWTARGETCREATION, LAST_CANVAS_EVENT_TYPE);
+ return mCurrentMemReader.good();
+}
+
+bool CanvasTranslator::ReadNextEvent(EventType& aEventType) {
+ if (mHeader->readerState == State::Paused) {
+ Flush();
+ return false;
+ }
+
+ uint32_t spinCount = mMaxSpinCount;
+ do {
+ if (HasPendingEvent()) {
+ return ReadPendingEvent(aEventType);
+ }
+ } while (--spinCount != 0);
+
+ Flush();
+ mHeader->readerState = State::AboutToWait;
+ if (HasPendingEvent()) {
+ mHeader->readerState = State::Processing;
+ return ReadPendingEvent(aEventType);
+ }
+
+ if (!mIsInTransaction) {
+ mHeader->readerState = State::Stopped;
+ return false;
+ }
+
+ // When in a transaction we wait for a short time because we're expecting more
+ // events from the content process. We don't want to wait for too long in case
+ // other content processes are waiting for events to process.
+ mHeader->readerState = State::Waiting;
+ if (mReaderSemaphore->Wait(Some(mNextEventTimeout))) {
+ MOZ_RELEASE_ASSERT(HasPendingEvent());
+ MOZ_RELEASE_ASSERT(mHeader->readerState == State::Processing);
+ return ReadPendingEvent(aEventType);
+ }
+
+ // We have to use compareExchange here because the writer can change our
+ // state if we are waiting.
+ if (!mHeader->readerState.compareExchange(State::Waiting, State::Stopped)) {
+ MOZ_RELEASE_ASSERT(HasPendingEvent());
+ MOZ_RELEASE_ASSERT(mHeader->readerState == State::Processing);
+ // The writer has just signaled us, so consume it before returning
+ MOZ_ALWAYS_TRUE(mReaderSemaphore->Wait());
+ return ReadPendingEvent(aEventType);
+ }
+
+ return false;
+}
+
+void CanvasTranslator::TranslateRecording() {
+ MOZ_ASSERT(IsInTaskQueue());
+
+ if (mSharedContext && EnsureSharedContextWebgl()) {
+ mSharedContext->EnterTlsScope();
+ }
+ auto exitTlsScope = MakeScopeExit([&] {
+ if (mSharedContext) {
+ mSharedContext->ExitTlsScope();
+ }
+ });
+
+ mHeader->readerState = State::Processing;
+ EventType eventType = EventType::INVALID;
+ while (ReadNextEvent(eventType)) {
+ bool success = RecordedEvent::DoWithEventFromReader(
+ mCurrentMemReader, eventType,
+ [&](RecordedEvent* recordedEvent) -> bool {
+ // Make sure that the whole event was read from the stream.
+ if (!mCurrentMemReader.good()) {
+ if (mIPDLClosed) {
+ // The other side has closed only warn about read failure.
+ gfxWarning() << "Failed to read event type: "
+ << recordedEvent->GetType();
+ } else {
+ gfxCriticalNote << "Failed to read event type: "
+ << recordedEvent->GetType();
+ }
+ mHeader->readerState = State::Failed;
+ return false;
+ }
+
+ return recordedEvent->PlayEvent(this);
+ });
+
+ // Check the stream is good here or we will log the issue twice.
+ if (!mCurrentMemReader.good()) {
+ return;
+ }
+
+ if (!success && !HandleExtensionEvent(eventType)) {
+ if (mDeviceResetInProgress) {
+ // We've notified the recorder of a device change, so we are expecting
+ // failures. Log as a warning to prevent crash reporting being flooded.
+ gfxWarning() << "Failed to play canvas event type: " << eventType;
+ } else {
+ gfxCriticalNote << "Failed to play canvas event type: " << eventType;
+ }
+ mHeader->readerState = State::Failed;
+ }
+
+ mHeader->processedCount++;
+ }
+}
+
+#define READ_AND_PLAY_CANVAS_EVENT_TYPE(_typeenum, _class) \
+ case _typeenum: { \
+ auto e = _class(mCurrentMemReader); \
+ if (!mCurrentMemReader.good()) { \
+ if (mIPDLClosed) { \
+ /* The other side has closed only warn about read failure. */ \
+ gfxWarning() << "Failed to read event type: " << _typeenum; \
+ } else { \
+ gfxCriticalNote << "Failed to read event type: " << _typeenum; \
+ } \
+ return false; \
+ } \
+ return e.PlayCanvasEvent(this); \
+ }
+
+bool CanvasTranslator::HandleExtensionEvent(int32_t aType) {
+ // This is where we handle extensions to the Moz2D Recording events to handle
+ // canvas specific things.
+ switch (aType) {
+ FOR_EACH_CANVAS_EVENT(READ_AND_PLAY_CANVAS_EVENT_TYPE)
+ default:
+ return false;
+ }
+}
+
+void CanvasTranslator::BeginTransaction() { mIsInTransaction = true; }
+
+void CanvasTranslator::Flush() {
+#if defined(XP_WIN)
+ // We can end up without a device, due to a reset and failure to re-create.
+ if (!mDevice) {
+ return;
+ }
+
+ gfx::AutoSerializeWithMoz2D serializeWithMoz2D(mBackendType);
+ RefPtr<ID3D11DeviceContext> deviceContext;
+ mDevice->GetImmediateContext(getter_AddRefs(deviceContext));
+ deviceContext->Flush();
+#endif
+}
+
+void CanvasTranslator::EndTransaction() {
+ Flush();
+ // At the end of a transaction is a good time to check if a new canvas device
+ // has been created, even if a reset did not occur.
+ Unused << CheckForFreshCanvasDevice(__LINE__);
+ mIsInTransaction = false;
+}
+
+void CanvasTranslator::DeviceChangeAcknowledged() {
+ mDeviceResetInProgress = false;
+ if (mRemoteTextureOwner) {
+ mRemoteTextureOwner->NotifyContextRestored();
+ }
+}
+
+bool CanvasTranslator::CreateReferenceTexture() {
+ if (mReferenceTextureData) {
+ mReferenceTextureData->Unlock();
+ }
+
+ mReferenceTextureData =
+ CreateTextureData(gfx::IntSize(1, 1), gfx::SurfaceFormat::B8G8R8A8, true);
+ if (!mReferenceTextureData) {
+ Deactivate();
+ return false;
+ }
+
+ if (NS_WARN_IF(!mReferenceTextureData->Lock(OpenMode::OPEN_READ_WRITE))) {
+ gfxCriticalNote << "CanvasTranslator::CreateReferenceTexture lock failed";
+ mReferenceTextureData.reset();
+ Deactivate();
+ return false;
+ }
+
+ mBaseDT = mReferenceTextureData->BorrowDrawTarget();
+
+ if (!mBaseDT) {
+ // We might get a null draw target due to a device failure, deactivate and
+ // return false so that we can recover.
+ Deactivate();
+ return false;
+ }
+
+ return true;
+}
+
+bool CanvasTranslator::CheckForFreshCanvasDevice(int aLineNumber) {
+ // If not on D3D11, we are not dependent on a fresh device for DT creation if
+ // one already exists.
+ if (mBaseDT && mTextureType != TextureType::D3D11) {
+ return false;
+ }
+
+#if defined(XP_WIN)
+ // If a new device has already been created, use that one.
+ RefPtr<ID3D11Device> device = gfx::DeviceManagerDx::Get()->GetCanvasDevice();
+ if (device && device != mDevice) {
+ if (mDevice) {
+ // We already had a device, notify child of change.
+ NotifyDeviceChanged();
+ }
+ mDevice = device.forget();
+ return CreateReferenceTexture();
+ }
+
+ if (mDevice) {
+ if (mDevice->GetDeviceRemovedReason() == S_OK) {
+ return false;
+ }
+
+ gfxCriticalNote << "GFX: CanvasTranslator detected a device reset at "
+ << aLineNumber;
+ NotifyDeviceChanged();
+ }
+
+ RefPtr<Runnable> runnable = NS_NewRunnableFunction(
+ "CanvasTranslator NotifyDeviceReset",
+ []() { gfx::GPUParent::GetSingleton()->NotifyDeviceReset(); });
+
+ // It is safe to wait here because only the Compositor thread waits on us and
+ // the main thread doesn't wait on the compositor thread in the GPU process.
+ SyncRunnable::DispatchToThread(GetMainThreadSerialEventTarget(), runnable,
+ /*aForceDispatch*/ true);
+
+ mDevice = gfx::DeviceManagerDx::Get()->GetCanvasDevice();
+ if (!mDevice) {
+ // We don't have a canvas device, we need to deactivate.
+ Telemetry::ScalarAdd(
+ Telemetry::ScalarID::GFX_CANVAS_REMOTE_DEACTIVATED_NO_DEVICE, 1);
+ Deactivate();
+ return false;
+ }
+#endif
+
+ return CreateReferenceTexture();
+}
+
+void CanvasTranslator::NotifyDeviceChanged() {
+ // Clear out any old recycled texture datas with the wrong device.
+ if (mRemoteTextureOwner) {
+ mRemoteTextureOwner->NotifyContextLost();
+ mRemoteTextureOwner->ClearRecycledTextures();
+ }
+ mDeviceResetInProgress = true;
+ gfx::CanvasRenderThread::Dispatch(
+ NewRunnableMethod("CanvasTranslator::SendNotifyDeviceChanged", this,
+ &CanvasTranslator::SendNotifyDeviceChanged));
+}
+
+gfx::DrawTargetWebgl* CanvasTranslator::GetDrawTargetWebgl(
+ int64_t aTextureId, bool aCheckForFallback) const {
+ auto result = mTextureInfo.find(aTextureId);
+ if (result != mTextureInfo.end()) {
+ return result->second.GetDrawTargetWebgl(aCheckForFallback);
+ }
+ return nullptr;
+}
+
+void CanvasTranslator::NotifyRequiresRefresh(int64_t aTextureId,
+ bool aDispatch) {
+ if (aDispatch) {
+ auto& info = mTextureInfo[aTextureId];
+ if (!info.mNotifiedRequiresRefresh) {
+ info.mNotifiedRequiresRefresh = true;
+ DispatchToTaskQueue(NewRunnableMethod<int64_t, bool>(
+ "CanvasTranslator::NotifyRequiresRefresh", this,
+ &CanvasTranslator::NotifyRequiresRefresh, aTextureId, false));
+ }
+ return;
+ }
+
+ if (mTextureInfo.find(aTextureId) != mTextureInfo.end()) {
+ Unused << SendNotifyRequiresRefresh(aTextureId);
+ }
+}
+
+void CanvasTranslator::CacheSnapshotShmem(int64_t aTextureId, bool aDispatch) {
+ if (aDispatch) {
+ DispatchToTaskQueue(NewRunnableMethod<int64_t, bool>(
+ "CanvasTranslator::CacheSnapshotShmem", this,
+ &CanvasTranslator::CacheSnapshotShmem, aTextureId, false));
+ return;
+ }
+
+ if (gfx::DrawTargetWebgl* webgl = GetDrawTargetWebgl(aTextureId)) {
+ if (auto shmemHandle = webgl->TakeShmemHandle()) {
+ // Lock the DT so that it doesn't get removed while shmem is in transit.
+ mTextureInfo[aTextureId].mLocked++;
+ nsCOMPtr<nsIThread> thread =
+ gfx::CanvasRenderThread::GetCanvasRenderThread();
+ RefPtr<CanvasTranslator> translator = this;
+ SendSnapshotShmem(aTextureId, std::move(shmemHandle),
+ webgl->GetShmemSize())
+ ->Then(
+ thread, __func__,
+ [=](bool) { translator->RemoveTexture(aTextureId); },
+ [=](ipc::ResponseRejectReason) {
+ translator->RemoveTexture(aTextureId);
+ });
+ }
+ }
+}
+
+void CanvasTranslator::PrepareShmem(int64_t aTextureId) {
+ if (gfx::DrawTargetWebgl* webgl = GetDrawTargetWebgl(aTextureId, false)) {
+ if (const auto& fallback = mTextureInfo[aTextureId].mTextureData) {
+ // If there was a fallback, copy the fallback to the software framebuffer
+ // shmem for reading.
+ if (RefPtr<gfx::DrawTarget> dt = fallback->BorrowDrawTarget()) {
+ if (RefPtr<gfx::SourceSurface> snapshot = dt->Snapshot()) {
+ webgl->CopySurface(snapshot, snapshot->GetRect(),
+ gfx::IntPoint(0, 0));
+ }
+ }
+ } else {
+ // Otherwise, just ensure the software framebuffer is up to date.
+ webgl->PrepareShmem();
+ }
+ }
+}
+
+void CanvasTranslator::ClearCachedResources() {
+ if (mSharedContext) {
+ // If there are any DrawTargetWebgls, then try to cache their framebuffers
+ // in software surfaces, just in case the GL context is lost. So long as
+ // there is a software copy of the framebuffer, it can be copied into a
+ // fallback TextureData later even if the GL context goes away.
+ mSharedContext->OnMemoryPressure();
+ for (auto const& entry : mTextureInfo) {
+ if (gfx::DrawTargetWebgl* webgl = entry.second.GetDrawTargetWebgl()) {
+ webgl->EnsureDataSnapshot();
+ }
+ }
+ }
+}
+
+ipc::IPCResult CanvasTranslator::RecvClearCachedResources() {
+ if (mDeactivated) {
+ // The other side might have sent a message before we deactivated.
+ return IPC_OK();
+ }
+
+ DispatchToTaskQueue(
+ NewRunnableMethod("CanvasTranslator::ClearCachedResources", this,
+ &CanvasTranslator::ClearCachedResources));
+ return IPC_OK();
+}
+
+static const OpenMode kInitMode = OpenMode::OPEN_READ_WRITE;
+
+already_AddRefed<gfx::DrawTarget> CanvasTranslator::CreateFallbackDrawTarget(
+ gfx::ReferencePtr aRefPtr, int64_t aTextureId,
+ RemoteTextureOwnerId aTextureOwnerId, const gfx::IntSize& aSize,
+ gfx::SurfaceFormat aFormat) {
+ RefPtr<gfx::DrawTarget> dt;
+ do {
+ UniquePtr<TextureData> textureData =
+ CreateOrRecycleTextureData(aSize, aFormat);
+ if (NS_WARN_IF(!textureData)) {
+ continue;
+ }
+
+ if (NS_WARN_IF(!textureData->Lock(kInitMode))) {
+ gfxCriticalNote << "CanvasTranslator::CreateDrawTarget lock failed";
+ continue;
+ }
+
+ dt = textureData->BorrowDrawTarget();
+ if (NS_WARN_IF(!dt)) {
+ textureData->Unlock();
+ continue;
+ }
+ // Recycled buffer contents may be uninitialized.
+ dt->ClearRect(gfx::Rect(dt->GetRect()));
+
+ TextureInfo& info = mTextureInfo[aTextureId];
+ info.mRefPtr = aRefPtr;
+ info.mTextureData = std::move(textureData);
+ info.mRemoteTextureOwnerId = aTextureOwnerId;
+ info.mTextureLockMode = kInitMode;
+ } while (!dt && CheckForFreshCanvasDevice(__LINE__));
+ return dt.forget();
+}
+
+already_AddRefed<gfx::DrawTarget> CanvasTranslator::CreateDrawTarget(
+ gfx::ReferencePtr aRefPtr, int64_t aTextureId,
+ RemoteTextureOwnerId aTextureOwnerId, const gfx::IntSize& aSize,
+ gfx::SurfaceFormat aFormat) {
+ if (aTextureId < 0) {
+ MOZ_DIAGNOSTIC_ASSERT(false, "No texture ID set");
+ return nullptr;
+ }
+
+ if (!aTextureOwnerId.IsValid()) {
+ MOZ_DIAGNOSTIC_ASSERT(false, "No texture owner set");
+ return nullptr;
+ }
+
+ RefPtr<gfx::DrawTarget> dt;
+ if (gfx::gfxVars::UseAcceleratedCanvas2D()) {
+ if (EnsureSharedContextWebgl()) {
+ mSharedContext->EnterTlsScope();
+ }
+ if (RefPtr<gfx::DrawTargetWebgl> webgl =
+ gfx::DrawTargetWebgl::Create(aSize, aFormat, mSharedContext)) {
+ webgl->BeginFrame(true);
+ dt = webgl.forget().downcast<gfx::DrawTarget>();
+ if (dt) {
+ TextureInfo& info = mTextureInfo[aTextureId];
+ info.mRefPtr = aRefPtr;
+ info.mDrawTarget = dt;
+ info.mRemoteTextureOwnerId = aTextureOwnerId;
+ info.mTextureLockMode = kInitMode;
+ CacheSnapshotShmem(aTextureId);
+ }
+ }
+ if (!dt) {
+ NotifyRequiresRefresh(aTextureId);
+ }
+ }
+
+ if (!dt) {
+ dt = CreateFallbackDrawTarget(aRefPtr, aTextureId, aTextureOwnerId, aSize,
+ aFormat);
+ }
+
+ AddDrawTarget(aRefPtr, dt);
+ return dt.forget();
+}
+
+already_AddRefed<gfx::DrawTarget> CanvasTranslator::CreateDrawTarget(
+ gfx::ReferencePtr aRefPtr, const gfx::IntSize& aSize,
+ gfx::SurfaceFormat aFormat) {
+ MOZ_DIAGNOSTIC_ASSERT(false, "Unexpected CreateDrawTarget call!");
+ return nullptr;
+}
+
+void CanvasTranslator::RemoveTexture(int64_t aTextureId,
+ RemoteTextureTxnType aTxnType,
+ RemoteTextureTxnId aTxnId) {
+ // Don't erase the texture if still in use
+ auto result = mTextureInfo.find(aTextureId);
+ if (result == mTextureInfo.end()) {
+ return;
+ }
+ auto& info = result->second;
+ if (mRemoteTextureOwner && aTxnType && aTxnId) {
+ mRemoteTextureOwner->WaitForTxn(info.mRemoteTextureOwnerId, aTxnType,
+ aTxnId);
+ }
+ if (--info.mLocked > 0) {
+ return;
+ }
+ if (info.mTextureData) {
+ info.mTextureData->Unlock();
+ }
+ if (mRemoteTextureOwner) {
+ // If this texture id was manually registered as a remote texture owner,
+ // unregister it so it does not stick around after the texture id goes away.
+ RemoteTextureOwnerId owner = info.mRemoteTextureOwnerId;
+ if (owner.IsValid()) {
+ mRemoteTextureOwner->UnregisterTextureOwner(owner);
+ }
+ }
+ mTextureInfo.erase(result);
+}
+
+bool CanvasTranslator::LockTexture(int64_t aTextureId, OpenMode aMode,
+ bool aInvalidContents) {
+ if (aMode == OpenMode::OPEN_NONE) {
+ return false;
+ }
+ auto result = mTextureInfo.find(aTextureId);
+ if (result == mTextureInfo.end()) {
+ return false;
+ }
+ auto& info = result->second;
+ if (info.mTextureLockMode != OpenMode::OPEN_NONE) {
+ return (info.mTextureLockMode & aMode) == aMode;
+ }
+ if (gfx::DrawTargetWebgl* webgl = info.GetDrawTargetWebgl()) {
+ if (aMode & OpenMode::OPEN_WRITE) {
+ webgl->BeginFrame(aInvalidContents);
+ }
+ }
+ info.mTextureLockMode = aMode;
+ return true;
+}
+
+bool CanvasTranslator::UnlockTexture(int64_t aTextureId) {
+ auto result = mTextureInfo.find(aTextureId);
+ if (result == mTextureInfo.end()) {
+ return false;
+ }
+ auto& info = result->second;
+ if (info.mTextureLockMode == OpenMode::OPEN_NONE) {
+ return false;
+ }
+
+ if (gfx::DrawTargetWebgl* webgl = info.GetDrawTargetWebgl()) {
+ if (info.mTextureLockMode & OpenMode::OPEN_WRITE) {
+ webgl->EndFrame();
+ if (webgl->RequiresRefresh()) {
+ NotifyRequiresRefresh(aTextureId);
+ }
+ }
+ }
+ info.mTextureLockMode = OpenMode::OPEN_NONE;
+ return true;
+}
+
+bool CanvasTranslator::PresentTexture(int64_t aTextureId, RemoteTextureId aId) {
+ auto result = mTextureInfo.find(aTextureId);
+ if (result == mTextureInfo.end()) {
+ return false;
+ }
+ auto& info = result->second;
+ RemoteTextureOwnerId ownerId = info.mRemoteTextureOwnerId;
+ if (gfx::DrawTargetWebgl* webgl = info.GetDrawTargetWebgl()) {
+ EnsureRemoteTextureOwner(ownerId);
+ if (webgl->CopyToSwapChain(aId, ownerId, mRemoteTextureOwner)) {
+ return true;
+ }
+ if (mSharedContext && mSharedContext->IsContextLost()) {
+ // If the context was lost, try to create a fallback to push instead.
+ EnsureSharedContextWebgl();
+ } else {
+ // CopyToSwapChain failed for an unknown reason other than context loss.
+ // Try to read into fallback data if possible to recover, otherwise force
+ // the loss of the individual texture.
+ webgl->EnsureDataSnapshot();
+ if (!TryDrawTargetWebglFallback(aTextureId, webgl)) {
+ RemoteTextureOwnerIdSet lost = {info.mRemoteTextureOwnerId};
+ mRemoteTextureOwner->NotifyContextLost(&lost);
+ }
+ }
+ }
+ if (TextureData* data = info.mTextureData.get()) {
+ PushRemoteTexture(aTextureId, data, aId, ownerId);
+ }
+ return true;
+}
+
+void CanvasTranslator::EnsureRemoteTextureOwner(RemoteTextureOwnerId aOwnerId) {
+ if (!mRemoteTextureOwner) {
+ mRemoteTextureOwner = new RemoteTextureOwnerClient(mOtherPid);
+ }
+ if (aOwnerId.IsValid() && !mRemoteTextureOwner->IsRegistered(aOwnerId)) {
+ mRemoteTextureOwner->RegisterTextureOwner(aOwnerId,
+ /* aSharedRecycling */ true);
+ }
+}
+
+UniquePtr<TextureData> CanvasTranslator::CreateOrRecycleTextureData(
+ const gfx::IntSize& aSize, gfx::SurfaceFormat aFormat) {
+ if (mRemoteTextureOwner) {
+ if (mTextureType == TextureType::Unknown) {
+ return mRemoteTextureOwner->CreateOrRecycleBufferTextureData(aSize,
+ aFormat);
+ }
+ if (UniquePtr<TextureData> data =
+ mRemoteTextureOwner->GetRecycledTextureData(aSize, aFormat,
+ mTextureType)) {
+ return data;
+ }
+ }
+ return CreateTextureData(aSize, aFormat, false);
+}
+
+bool CanvasTranslator::PushRemoteTexture(int64_t aTextureId, TextureData* aData,
+ RemoteTextureId aId,
+ RemoteTextureOwnerId aOwnerId) {
+ EnsureRemoteTextureOwner(aOwnerId);
+ UniquePtr<TextureData> dstData;
+ if (!mDeviceResetInProgress) {
+ TextureData::Info info;
+ aData->FillInfo(info);
+ dstData = CreateOrRecycleTextureData(info.size, info.format);
+ }
+ bool success = false;
+ // Source data is already locked.
+ if (dstData) {
+ if (dstData->Lock(OpenMode::OPEN_WRITE)) {
+ if (RefPtr<gfx::DrawTarget> dstDT = dstData->BorrowDrawTarget()) {
+ if (RefPtr<gfx::DrawTarget> srcDT = aData->BorrowDrawTarget()) {
+ if (RefPtr<gfx::SourceSurface> snapshot = srcDT->Snapshot()) {
+ dstDT->CopySurface(snapshot, snapshot->GetRect(),
+ gfx::IntPoint(0, 0));
+ dstDT->Flush();
+ success = true;
+ }
+ }
+ }
+ dstData->Unlock();
+ } else {
+ gfxCriticalNote << "CanvasTranslator::PushRemoteTexture dst lock failed";
+ }
+ }
+ if (success) {
+ mRemoteTextureOwner->PushTexture(aId, aOwnerId, std::move(dstData));
+ } else {
+ mRemoteTextureOwner->PushDummyTexture(aId, aOwnerId);
+ }
+ return success;
+}
+
+void CanvasTranslator::ClearTextureInfo() {
+ for (auto const& entry : mTextureInfo) {
+ if (entry.second.mTextureData) {
+ entry.second.mTextureData->Unlock();
+ }
+ }
+ mTextureInfo.clear();
+ mDrawTargets.Clear();
+ mSharedContext = nullptr;
+ mBaseDT = nullptr;
+ if (mReferenceTextureData) {
+ mReferenceTextureData->Unlock();
+ }
+ if (mRemoteTextureOwner) {
+ mRemoteTextureOwner->UnregisterAllTextureOwners();
+ mRemoteTextureOwner = nullptr;
+ }
+ if (mTranslationTaskQueue) {
+ gfx::CanvasRenderThread::FinishShutdownWorkerTaskQueue(
+ mTranslationTaskQueue);
+ }
+}
+
+already_AddRefed<gfx::SourceSurface> CanvasTranslator::LookupExternalSurface(
+ uint64_t aKey) {
+ return mSharedSurfacesHolder->Get(wr::ToExternalImageId(aKey));
+}
+
+void CanvasTranslator::CheckpointReached() { CheckAndSignalWriter(); }
+
+void CanvasTranslator::PauseTranslation() {
+ mHeader->readerState = State::Paused;
+}
+
+already_AddRefed<gfx::GradientStops> CanvasTranslator::GetOrCreateGradientStops(
+ gfx::DrawTarget* aDrawTarget, gfx::GradientStop* aRawStops,
+ uint32_t aNumStops, gfx::ExtendMode aExtendMode) {
+ MOZ_ASSERT(aDrawTarget);
+ nsTArray<gfx::GradientStop> rawStopArray(aRawStops, aNumStops);
+ return gfx::gfxGradientCache::GetOrCreateGradientStops(
+ aDrawTarget, rawStopArray, aExtendMode);
+}
+
+gfx::DataSourceSurface* CanvasTranslator::LookupDataSurface(
+ gfx::ReferencePtr aRefPtr) {
+ return mDataSurfaces.GetWeak(aRefPtr);
+}
+
+void CanvasTranslator::AddDataSurface(
+ gfx::ReferencePtr aRefPtr, RefPtr<gfx::DataSourceSurface>&& aSurface) {
+ mDataSurfaces.InsertOrUpdate(aRefPtr, std::move(aSurface));
+}
+
+void CanvasTranslator::RemoveDataSurface(gfx::ReferencePtr aRefPtr) {
+ mDataSurfaces.Remove(aRefPtr);
+}
+
+void CanvasTranslator::SetPreparedMap(
+ gfx::ReferencePtr aSurface,
+ UniquePtr<gfx::DataSourceSurface::ScopedMap> aMap) {
+ mMappedSurface = aSurface;
+ mPreparedMap = std::move(aMap);
+}
+
+UniquePtr<gfx::DataSourceSurface::ScopedMap> CanvasTranslator::GetPreparedMap(
+ gfx::ReferencePtr aSurface) {
+ if (!mPreparedMap) {
+ // We might fail to set the map during, for example, device resets.
+ return nullptr;
+ }
+
+ MOZ_RELEASE_ASSERT(mMappedSurface == aSurface,
+ "aSurface must match previously stored surface.");
+
+ mMappedSurface = nullptr;
+ return std::move(mPreparedMap);
+}
+
+} // namespace layers
+} // namespace mozilla