summaryrefslogtreecommitdiffstats
path: root/gfx/layers/ipc/CanvasChild.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/layers/ipc/CanvasChild.cpp')
-rw-r--r--gfx/layers/ipc/CanvasChild.cpp345
1 files changed, 345 insertions, 0 deletions
diff --git a/gfx/layers/ipc/CanvasChild.cpp b/gfx/layers/ipc/CanvasChild.cpp
new file mode 100644
index 0000000000..2f8464402d
--- /dev/null
+++ b/gfx/layers/ipc/CanvasChild.cpp
@@ -0,0 +1,345 @@
+/* -*- 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<CanvasChild> 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<CanvasChild> mCanvasChild;
+};
+
+class SourceSurfaceCanvasRecording final : public gfx::SourceSurface {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(SourceSurfaceCanvasRecording, final)
+
+ SourceSurfaceCanvasRecording(
+ const RefPtr<gfx::SourceSurface>& aRecordedSuface,
+ CanvasChild* aCanvasChild,
+ const RefPtr<CanvasDrawEventRecorder>& 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<gfx::DataSourceSurface> 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<CanvasDrawEventRecorder> aRecorder,
+ ReferencePtr aSurfaceAlias,
+ RefPtr<gfx::SourceSurface> aAliasedSurface,
+ RefPtr<CanvasChild> aCanvasChild) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ aRecorder->RemoveStoredObject(aSurfaceAlias);
+ aRecorder->RecordEvent(RecordedRemoveSurfaceAlias(aSurfaceAlias));
+ aAliasedSurface = nullptr;
+ aCanvasChild = nullptr;
+ aRecorder = nullptr;
+ }
+
+ RefPtr<gfx::SourceSurface> mRecordedSurface;
+ RefPtr<CanvasChild> mCanvasChild;
+ RefPtr<CanvasDrawEventRecorder> mRecorder;
+ RefPtr<gfx::DataSourceSurface> mDataSourceSurface;
+};
+
+CanvasChild::CanvasChild(Endpoint<PCanvasChild>&& aEndpoint) {
+ aEndpoint.Bind(this);
+}
+
+CanvasChild::~CanvasChild() = default;
+
+static void NotifyCanvasDeviceReset() {
+ nsCOMPtr<nsIObserverService> 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<CanvasDrawEventRecorder>();
+ SharedMemoryBasic::Handle handle;
+ CrossProcessSemaphoreHandle readerSem;
+ CrossProcessSemaphoreHandle writerSem;
+ if (!mRecorder->Init(OtherPid(), &handle, &readerSem, &writerSem,
+ MakeUnique<RingBufferWriterServices>(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<gfx::DrawTarget> CanvasChild::CreateDrawTarget(
+ gfx::IntSize aSize, gfx::SurfaceFormat aFormat) {
+ // We drop mRecorder in ActorDestroy to break the reference cycle.
+ if (!mRecorder) {
+ return nullptr;
+ }
+
+ RefPtr<gfx::DrawTarget> dummyDt = gfx::Factory::CreateDrawTarget(
+ gfx::BackendType::SKIA, gfx::IntSize(1, 1), aFormat);
+ RefPtr<gfx::DrawTarget> dt = MakeAndAddRef<gfx::DrawTargetRecording>(
+ 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<gfx::DataSourceSurface> 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<gfx::DataSourceSurface> 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<char*>(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<gfx::SourceSurface> CanvasChild::WrapSurface(
+ const RefPtr<gfx::SourceSurface>& aSurface) {
+ MOZ_ASSERT(aSurface);
+ // We drop mRecorder in ActorDestroy to break the reference cycle.
+ if (!mRecorder) {
+ return nullptr;
+ }
+
+ return MakeAndAddRef<SourceSurfaceCanvasRecording>(aSurface, this, mRecorder);
+}
+
+} // namespace layers
+} // namespace mozilla