/* -*- 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 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 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& aShmem, Handle&& aHandle, ipc::SharedMemory::OpenRights aOpenRights, size_t aSize) { auto shmem = MakeRefPtr(); 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&& 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(); 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(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( "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( "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(aSurfaceRef); gfx::SourceSurface* surface = LookupSourceSurface(surfaceRef); if (!surface) { return; } UniquePtr 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(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(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 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 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 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 = 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( "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( "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 thread = gfx::CanvasRenderThread::GetCanvasRenderThread(); RefPtr 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 dt = fallback->BorrowDrawTarget()) { if (RefPtr 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 CanvasTranslator::CreateFallbackDrawTarget( gfx::ReferencePtr aRefPtr, int64_t aTextureId, RemoteTextureOwnerId aTextureOwnerId, const gfx::IntSize& aSize, gfx::SurfaceFormat aFormat) { RefPtr dt; do { UniquePtr 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 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 dt; if (gfx::gfxVars::UseAcceleratedCanvas2D()) { if (EnsureSharedContextWebgl()) { mSharedContext->EnterTlsScope(); } if (RefPtr webgl = gfx::DrawTargetWebgl::Create(aSize, aFormat, mSharedContext)) { webgl->BeginFrame(true); dt = webgl.forget().downcast(); 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 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 CanvasTranslator::CreateOrRecycleTextureData( const gfx::IntSize& aSize, gfx::SurfaceFormat aFormat) { if (mRemoteTextureOwner) { if (mTextureType == TextureType::Unknown) { return mRemoteTextureOwner->CreateOrRecycleBufferTextureData(aSize, aFormat); } if (UniquePtr 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 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 dstDT = dstData->BorrowDrawTarget()) { if (RefPtr srcDT = aData->BorrowDrawTarget()) { if (RefPtr 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 CanvasTranslator::LookupExternalSurface( uint64_t aKey) { return mSharedSurfacesHolder->Get(wr::ToExternalImageId(aKey)); } void CanvasTranslator::CheckpointReached() { CheckAndSignalWriter(); } void CanvasTranslator::PauseTranslation() { mHeader->readerState = State::Paused; } already_AddRefed CanvasTranslator::GetOrCreateGradientStops( gfx::DrawTarget* aDrawTarget, gfx::GradientStop* aRawStops, uint32_t aNumStops, gfx::ExtendMode aExtendMode) { MOZ_ASSERT(aDrawTarget); nsTArray 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&& aSurface) { mDataSurfaces.InsertOrUpdate(aRefPtr, std::move(aSurface)); } void CanvasTranslator::RemoveDataSurface(gfx::ReferencePtr aRefPtr) { mDataSurfaces.Remove(aRefPtr); } void CanvasTranslator::SetPreparedMap( gfx::ReferencePtr aSurface, UniquePtr aMap) { mMappedSurface = aSurface; mPreparedMap = std::move(aMap); } UniquePtr 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