/* -*- 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 http://mozilla.org/MPL/2.0/. */ #include "CanvasChild.h" #include "MainThreadUtils.h" #include "mozilla/gfx/DrawTargetRecording.h" #include "mozilla/gfx/Tools.h" #include "mozilla/gfx/Rect.h" #include "mozilla/gfx/Point.h" #include "mozilla/ipc/Endpoint.h" #include "mozilla/ipc/ProcessChild.h" #include "mozilla/layers/CanvasDrawEventRecorder.h" #include "nsIObserverService.h" #include "RecordedCanvasEventImpl.h" namespace mozilla { namespace layers { class RingBufferWriterServices final : public CanvasEventRingBuffer::WriterServices { public: explicit RingBufferWriterServices(RefPtr aCanvasChild) : mCanvasChild(std::move(aCanvasChild)) {} ~RingBufferWriterServices() final = default; bool ReaderClosed() final { return !mCanvasChild->GetIPCChannel()->CanSend() || ipc::ProcessChild::ExpectingShutdown(); } void ResumeReader() final { mCanvasChild->ResumeTranslation(); } private: RefPtr mCanvasChild; }; class SourceSurfaceCanvasRecording final : public gfx::SourceSurface { public: MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(SourceSurfaceCanvasRecording, final) SourceSurfaceCanvasRecording( const RefPtr& aRecordedSuface, CanvasChild* aCanvasChild, const RefPtr& aRecorder) : mRecordedSurface(aRecordedSuface), mCanvasChild(aCanvasChild), mRecorder(aRecorder) { // It's important that AddStoredObject is called first because that will // run any pending processing required by recorded objects that have been // deleted off the main thread. mRecorder->AddStoredObject(this); mRecorder->RecordEvent(RecordedAddSurfaceAlias(this, aRecordedSuface)); } ~SourceSurfaceCanvasRecording() { ReferencePtr surfaceAlias = this; if (NS_IsMainThread()) { ReleaseOnMainThread(std::move(mRecorder), surfaceAlias, std::move(mRecordedSurface), std::move(mCanvasChild)); return; } mRecorder->AddPendingDeletion( [recorder = std::move(mRecorder), surfaceAlias, aliasedSurface = std::move(mRecordedSurface), canvasChild = std::move(mCanvasChild)]() mutable -> void { ReleaseOnMainThread(std::move(recorder), surfaceAlias, std::move(aliasedSurface), std::move(canvasChild)); }); } gfx::SurfaceType GetType() const final { return mRecordedSurface->GetType(); } gfx::IntSize GetSize() const final { return mRecordedSurface->GetSize(); } gfx::SurfaceFormat GetFormat() const final { return mRecordedSurface->GetFormat(); } already_AddRefed GetDataSurface() final { EnsureDataSurfaceOnMainThread(); return do_AddRef(mDataSourceSurface); } private: void EnsureDataSurfaceOnMainThread() { // The data can only be retrieved on the main thread. if (!mDataSourceSurface && NS_IsMainThread()) { mDataSourceSurface = mCanvasChild->GetDataSurface(mRecordedSurface); } } // Used to ensure that clean-up that requires it is done on the main thread. static void ReleaseOnMainThread(RefPtr aRecorder, ReferencePtr aSurfaceAlias, RefPtr aAliasedSurface, RefPtr aCanvasChild) { MOZ_ASSERT(NS_IsMainThread()); aRecorder->RemoveStoredObject(aSurfaceAlias); aRecorder->RecordEvent(RecordedRemoveSurfaceAlias(aSurfaceAlias)); aAliasedSurface = nullptr; aCanvasChild = nullptr; aRecorder = nullptr; } RefPtr mRecordedSurface; RefPtr mCanvasChild; RefPtr mRecorder; RefPtr mDataSourceSurface; }; CanvasChild::CanvasChild(Endpoint&& aEndpoint) { aEndpoint.Bind(this); } CanvasChild::~CanvasChild() = default; static void NotifyCanvasDeviceReset() { nsCOMPtr obs = services::GetObserverService(); if (obs) { obs->NotifyObservers(nullptr, "canvas-device-reset", nullptr); } } ipc::IPCResult CanvasChild::RecvNotifyDeviceChanged() { NotifyCanvasDeviceReset(); mRecorder->RecordEvent(RecordedDeviceChangeAcknowledged()); return IPC_OK(); } /* static */ bool CanvasChild::mDeactivated = false; ipc::IPCResult CanvasChild::RecvDeactivate() { mDeactivated = true; NotifyCanvasDeviceReset(); return IPC_OK(); } void CanvasChild::EnsureRecorder(TextureType aTextureType) { if (!mRecorder) { MOZ_ASSERT(mTextureType == TextureType::Unknown); mTextureType = aTextureType; mRecorder = MakeAndAddRef(); SharedMemoryBasic::Handle handle; CrossProcessSemaphoreHandle readerSem; CrossProcessSemaphoreHandle writerSem; if (!mRecorder->Init(OtherPid(), &handle, &readerSem, &writerSem, MakeUnique(this))) { mRecorder = nullptr; return; } if (CanSend()) { Unused << SendInitTranslator(mTextureType, std::move(handle), std::move(readerSem), std::move(writerSem)); } } MOZ_RELEASE_ASSERT(mTextureType == aTextureType, "We only support one remote TextureType currently."); } void CanvasChild::ActorDestroy(ActorDestroyReason aWhy) { // Explicitly drop our reference to the recorder, because it holds a reference // to us via the ResumeTranslation callback. mRecorder = nullptr; } void CanvasChild::ResumeTranslation() { if (CanSend()) { SendResumeTranslation(); } } void CanvasChild::Destroy() { if (CanSend()) { Close(); } } void CanvasChild::OnTextureWriteLock() { // We drop mRecorder in ActorDestroy to break the reference cycle. if (!mRecorder) { return; } mHasOutstandingWriteLock = true; mLastWriteLockCheckpoint = mRecorder->CreateCheckpoint(); } void CanvasChild::OnTextureForwarded() { // We drop mRecorder in ActorDestroy to break the reference cycle. if (!mRecorder) { return; } if (mHasOutstandingWriteLock) { mRecorder->RecordEvent(RecordedCanvasFlush()); if (!mRecorder->WaitForCheckpoint(mLastWriteLockCheckpoint)) { gfxWarning() << "Timed out waiting for last write lock to be processed."; } mHasOutstandingWriteLock = false; } // We hold onto the last transaction's external surfaces until we have waited // for the write locks in this transaction. This means we know that the // surfaces have been picked up in the canvas threads and there is no race // with them being removed from SharedSurfacesParent. Note this releases the // current contents of mLastTransactionExternalSurfaces. mRecorder->TakeExternalSurfaces(mLastTransactionExternalSurfaces); } void CanvasChild::EnsureBeginTransaction() { // We drop mRecorder in ActorDestroy to break the reference cycle. if (!mRecorder) { return; } if (!mIsInTransaction) { mRecorder->RecordEvent(RecordedCanvasBeginTransaction()); mIsInTransaction = true; } } void CanvasChild::EndTransaction() { // We drop mRecorder in ActorDestroy to break the reference cycle. if (!mRecorder) { return; } if (mIsInTransaction) { mRecorder->RecordEvent(RecordedCanvasEndTransaction()); mIsInTransaction = false; mLastNonEmptyTransaction = TimeStamp::NowLoRes(); } ++mTransactionsSinceGetDataSurface; } bool CanvasChild::ShouldBeCleanedUp() const { // Always return true if we've been deactivated. if (Deactivated()) { return true; } // We can only be cleaned up if nothing else references our recorder. if (mRecorder && !mRecorder->hasOneRef()) { return false; } static const TimeDuration kCleanUpCanvasThreshold = TimeDuration::FromSeconds(10); return TimeStamp::NowLoRes() - mLastNonEmptyTransaction > kCleanUpCanvasThreshold; } already_AddRefed CanvasChild::CreateDrawTarget( gfx::IntSize aSize, gfx::SurfaceFormat aFormat) { // We drop mRecorder in ActorDestroy to break the reference cycle. if (!mRecorder) { return nullptr; } RefPtr dummyDt = gfx::Factory::CreateDrawTarget( gfx::BackendType::SKIA, gfx::IntSize(1, 1), aFormat); RefPtr dt = MakeAndAddRef( mRecorder, dummyDt, gfx::IntRect(gfx::IntPoint(0, 0), aSize)); return dt.forget(); } void CanvasChild::RecordEvent(const gfx::RecordedEvent& aEvent) { // We drop mRecorder in ActorDestroy to break the reference cycle. if (!mRecorder) { return; } mRecorder->RecordEvent(aEvent); } already_AddRefed CanvasChild::GetDataSurface( const gfx::SourceSurface* aSurface) { MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aSurface); // We drop mRecorder in ActorDestroy to break the reference cycle. if (!mRecorder) { return nullptr; } // mTransactionsSinceGetDataSurface is used to determine if we want to prepare // a DataSourceSurface in the GPU process up front at the end of the // transaction, but that only makes sense if the canvas JS is requesting data // in between transactions. if (!mIsInTransaction) { mTransactionsSinceGetDataSurface = 0; } EnsureBeginTransaction(); mRecorder->RecordEvent(RecordedPrepareDataForSurface(aSurface)); uint32_t checkpoint = mRecorder->CreateCheckpoint(); gfx::IntSize ssSize = aSurface->GetSize(); gfx::SurfaceFormat ssFormat = aSurface->GetFormat(); size_t dataFormatWidth = ssSize.width * BytesPerPixel(ssFormat); RefPtr dataSurface = gfx::Factory::CreateDataSourceSurfaceWithStride(ssSize, ssFormat, dataFormatWidth); if (!dataSurface) { gfxWarning() << "Failed to create DataSourceSurface."; return nullptr; } gfx::DataSourceSurface::ScopedMap map(dataSurface, gfx::DataSourceSurface::READ_WRITE); char* dest = reinterpret_cast(map.GetData()); if (!mRecorder->WaitForCheckpoint(checkpoint)) { gfxWarning() << "Timed out preparing data for DataSourceSurface."; return dataSurface.forget(); } mRecorder->RecordEvent(RecordedGetDataForSurface(aSurface)); mRecorder->ReturnRead(dest, ssSize.height * dataFormatWidth); return dataSurface.forget(); } already_AddRefed CanvasChild::WrapSurface( const RefPtr& aSurface) { MOZ_ASSERT(aSurface); // We drop mRecorder in ActorDestroy to break the reference cycle. if (!mRecorder) { return nullptr; } return MakeAndAddRef(aSurface, this, mRecorder); } } // namespace layers } // namespace mozilla