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.cpp547
1 files changed, 547 insertions, 0 deletions
diff --git a/gfx/layers/ipc/CanvasChild.cpp b/gfx/layers/ipc/CanvasChild.cpp
new file mode 100644
index 0000000000..553217d82b
--- /dev/null
+++ b/gfx/layers/ipc/CanvasChild.cpp
@@ -0,0 +1,547 @@
+/* -*- 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/CanvasManagerChild.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 "mozilla/layers/ImageDataSerializer.h"
+#include "mozilla/layers/SourceSurfaceSharedData.h"
+#include "mozilla/Maybe.h"
+#include "nsIObserverService.h"
+#include "RecordedCanvasEventImpl.h"
+
+namespace mozilla {
+namespace layers {
+
+class RecorderHelpers final : public CanvasDrawEventRecorder::Helpers {
+ public:
+ NS_DECL_OWNINGTHREAD
+
+ explicit RecorderHelpers(const RefPtr<CanvasChild>& aCanvasChild)
+ : mCanvasChild(aCanvasChild) {}
+
+ ~RecorderHelpers() override = default;
+
+ bool InitTranslator(TextureType aTextureType, gfx::BackendType aBackendType,
+ Handle&& aReadHandle, nsTArray<Handle>&& aBufferHandles,
+ uint64_t aBufferSize,
+ CrossProcessSemaphoreHandle&& aReaderSem,
+ CrossProcessSemaphoreHandle&& aWriterSem) override {
+ NS_ASSERT_OWNINGTHREAD(RecorderHelpers);
+ if (NS_WARN_IF(!mCanvasChild)) {
+ return false;
+ }
+ return mCanvasChild->SendInitTranslator(
+ aTextureType, aBackendType, std::move(aReadHandle),
+ std::move(aBufferHandles), aBufferSize, std::move(aReaderSem),
+ std::move(aWriterSem));
+ }
+
+ bool AddBuffer(Handle&& aBufferHandle, uint64_t aBufferSize) override {
+ NS_ASSERT_OWNINGTHREAD(RecorderHelpers);
+ if (!mCanvasChild) {
+ return false;
+ }
+ return mCanvasChild->SendAddBuffer(std::move(aBufferHandle), aBufferSize);
+ }
+
+ bool ReaderClosed() override {
+ NS_ASSERT_OWNINGTHREAD(RecorderHelpers);
+ if (!mCanvasChild) {
+ return false;
+ }
+ return !mCanvasChild->CanSend() || ipc::ProcessChild::ExpectingShutdown();
+ }
+
+ bool RestartReader() override {
+ NS_ASSERT_OWNINGTHREAD(RecorderHelpers);
+ if (!mCanvasChild) {
+ return false;
+ }
+ return mCanvasChild->SendRestartTranslation();
+ }
+
+ private:
+ const WeakPtr<CanvasChild> mCanvasChild;
+};
+
+class SourceSurfaceCanvasRecording final : public gfx::SourceSurface {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(SourceSurfaceCanvasRecording, final)
+
+ SourceSurfaceCanvasRecording(
+ int64_t aTextureId, const RefPtr<gfx::SourceSurface>& aRecordedSuface,
+ CanvasChild* aCanvasChild,
+ const RefPtr<CanvasDrawEventRecorder>& aRecorder)
+ : mTextureId(aTextureId),
+ 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);
+ }
+
+ void AttachSurface() { mDetached = false; }
+ void DetachSurface() { mDetached = true; }
+
+ already_AddRefed<gfx::SourceSurface> ExtractSubrect(
+ const gfx::IntRect& aRect) final {
+ return mRecordedSurface->ExtractSubrect(aRect);
+ }
+
+ private:
+ void EnsureDataSurfaceOnMainThread() {
+ // The data can only be retrieved on the main thread.
+ if (!mDataSourceSurface && NS_IsMainThread()) {
+ mDataSourceSurface =
+ mCanvasChild->GetDataSurface(mTextureId, mRecordedSurface, mDetached);
+ }
+ }
+
+ // 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;
+ }
+
+ int64_t mTextureId;
+ RefPtr<gfx::SourceSurface> mRecordedSurface;
+ RefPtr<CanvasChild> mCanvasChild;
+ RefPtr<CanvasDrawEventRecorder> mRecorder;
+ RefPtr<gfx::DataSourceSurface> mDataSourceSurface;
+ bool mDetached = false;
+};
+
+CanvasChild::CanvasChild() = default;
+
+CanvasChild::~CanvasChild() = default;
+
+static void NotifyCanvasDeviceReset() {
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (obs) {
+ obs->NotifyObservers(nullptr, "canvas-device-reset", nullptr);
+ }
+}
+
+ipc::IPCResult CanvasChild::RecvNotifyDeviceChanged() {
+ NS_ASSERT_OWNINGTHREAD(CanvasChild);
+
+ NotifyCanvasDeviceReset();
+ mRecorder->RecordEvent(RecordedDeviceChangeAcknowledged());
+ return IPC_OK();
+}
+
+/* static */ bool CanvasChild::mDeactivated = false;
+
+ipc::IPCResult CanvasChild::RecvDeactivate() {
+ NS_ASSERT_OWNINGTHREAD(CanvasChild);
+
+ RefPtr<CanvasChild> self(this);
+ mDeactivated = true;
+ if (auto* cm = gfx::CanvasManagerChild::Get()) {
+ cm->DeactivateCanvas();
+ }
+ NotifyCanvasDeviceReset();
+ return IPC_OK();
+}
+
+ipc::IPCResult CanvasChild::RecvBlockCanvas() {
+ mBlocked = true;
+ if (auto* cm = gfx::CanvasManagerChild::Get()) {
+ cm->BlockCanvas();
+ }
+ return IPC_OK();
+}
+
+void CanvasChild::EnsureRecorder(gfx::IntSize aSize, gfx::SurfaceFormat aFormat,
+ TextureType aTextureType) {
+ NS_ASSERT_OWNINGTHREAD(CanvasChild);
+
+ if (!mRecorder) {
+ gfx::BackendType backendType =
+ gfxPlatform::GetPlatform()->GetPreferredCanvasBackend();
+ auto recorder = MakeRefPtr<CanvasDrawEventRecorder>();
+ if (!recorder->Init(aTextureType, backendType,
+ MakeUnique<RecorderHelpers>(this))) {
+ return;
+ }
+
+ mRecorder = recorder.forget();
+ }
+
+ MOZ_RELEASE_ASSERT(mRecorder->GetTextureType() == aTextureType,
+ "We only support one remote TextureType currently.");
+
+ EnsureDataSurfaceShmem(aSize, aFormat);
+}
+
+void CanvasChild::ActorDestroy(ActorDestroyReason aWhy) {
+ NS_ASSERT_OWNINGTHREAD(CanvasChild);
+
+ if (mRecorder) {
+ mRecorder->DetachResources();
+ }
+}
+
+void CanvasChild::Destroy() {
+ NS_ASSERT_OWNINGTHREAD(CanvasChild);
+
+ if (CanSend()) {
+ Send__delete__(this);
+ }
+}
+
+bool CanvasChild::EnsureBeginTransaction() {
+ NS_ASSERT_OWNINGTHREAD(CanvasChild);
+
+ if (!mIsInTransaction) {
+ RecordEvent(RecordedCanvasBeginTransaction());
+ mIsInTransaction = true;
+ }
+
+ return true;
+}
+
+void CanvasChild::EndTransaction() {
+ NS_ASSERT_OWNINGTHREAD(CanvasChild);
+
+ if (mIsInTransaction) {
+ RecordEvent(RecordedCanvasEndTransaction());
+ mIsInTransaction = false;
+ mDormant = false;
+ } else if (mRecorder) {
+ // Schedule to drop free buffers if we have no non-empty transactions.
+ if (!mDormant) {
+ mDormant = true;
+ NS_DelayedDispatchToCurrentThread(
+ NewRunnableMethod("CanvasChild::DropFreeBuffersWhenDormant", this,
+ &CanvasChild::DropFreeBuffersWhenDormant),
+ StaticPrefs::gfx_canvas_remote_drop_buffer_milliseconds());
+ }
+ }
+
+ ++mTransactionsSinceGetDataSurface;
+}
+
+void CanvasChild::DropFreeBuffersWhenDormant() {
+ NS_ASSERT_OWNINGTHREAD(CanvasChild);
+ // Drop any free buffers if we have not had any non-empty transactions.
+ if (mDormant && mRecorder) {
+ mRecorder->DropFreeBuffers();
+ }
+}
+
+void CanvasChild::ClearCachedResources() {
+ NS_ASSERT_OWNINGTHREAD(CanvasChild);
+ if (mRecorder) {
+ mRecorder->DropFreeBuffers();
+ // Notify CanvasTranslator it is about to be minimized.
+ SendClearCachedResources();
+ }
+}
+
+bool CanvasChild::ShouldBeCleanedUp() const {
+ NS_ASSERT_OWNINGTHREAD(CanvasChild);
+
+ // Always return true if we've been deactivated.
+ if (Deactivated()) {
+ return true;
+ }
+
+ // We can only be cleaned up if nothing else references our recorder.
+ return !mRecorder || mRecorder->hasOneRef();
+}
+
+already_AddRefed<gfx::DrawTargetRecording> CanvasChild::CreateDrawTarget(
+ int64_t aTextureId, const RemoteTextureOwnerId& aTextureOwnerId,
+ gfx::IntSize aSize, gfx::SurfaceFormat aFormat) {
+ NS_ASSERT_OWNINGTHREAD(CanvasChild);
+
+ if (!mRecorder) {
+ return nullptr;
+ }
+
+ RefPtr<gfx::DrawTarget> dummyDt = gfx::Factory::CreateDrawTarget(
+ gfx::BackendType::SKIA, gfx::IntSize(1, 1), aFormat);
+ RefPtr<gfx::DrawTargetRecording> dt = MakeAndAddRef<gfx::DrawTargetRecording>(
+ mRecorder, aTextureId, aTextureOwnerId, dummyDt, aSize);
+
+ mTextureInfo.insert({aTextureId, {}});
+
+ return dt.forget();
+}
+
+bool CanvasChild::EnsureDataSurfaceShmem(gfx::IntSize aSize,
+ gfx::SurfaceFormat aFormat) {
+ NS_ASSERT_OWNINGTHREAD(CanvasChild);
+
+ if (!mRecorder) {
+ return false;
+ }
+
+ size_t sizeRequired =
+ ImageDataSerializer::ComputeRGBBufferSize(aSize, aFormat);
+ if (!sizeRequired) {
+ return false;
+ }
+ sizeRequired = ipc::SharedMemory::PageAlignedSize(sizeRequired);
+
+ if (!mDataSurfaceShmemAvailable || mDataSurfaceShmem->Size() < sizeRequired) {
+ RecordEvent(RecordedPauseTranslation());
+ auto dataSurfaceShmem = MakeRefPtr<ipc::SharedMemoryBasic>();
+ if (!dataSurfaceShmem->Create(sizeRequired) ||
+ !dataSurfaceShmem->Map(sizeRequired)) {
+ return false;
+ }
+
+ auto shmemHandle = dataSurfaceShmem->TakeHandle();
+ if (!shmemHandle) {
+ return false;
+ }
+
+ if (!SendSetDataSurfaceBuffer(std::move(shmemHandle), sizeRequired)) {
+ return false;
+ }
+
+ mDataSurfaceShmem = dataSurfaceShmem.forget();
+ mDataSurfaceShmemAvailable = true;
+ }
+
+ MOZ_ASSERT(mDataSurfaceShmemAvailable);
+ return true;
+}
+
+void CanvasChild::RecordEvent(const gfx::RecordedEvent& aEvent) {
+ NS_ASSERT_OWNINGTHREAD(CanvasChild);
+
+ // We drop mRecorder in ActorDestroy to break the reference cycle.
+ if (!mRecorder) {
+ return;
+ }
+
+ mRecorder->RecordEvent(aEvent);
+}
+
+int64_t CanvasChild::CreateCheckpoint() {
+ NS_ASSERT_OWNINGTHREAD(CanvasChild);
+ return mRecorder->CreateCheckpoint();
+}
+
+already_AddRefed<gfx::DataSourceSurface> CanvasChild::GetDataSurface(
+ int64_t aTextureId, const gfx::SourceSurface* aSurface, bool aDetached) {
+ NS_ASSERT_OWNINGTHREAD(CanvasChild);
+ MOZ_ASSERT(aSurface);
+
+ // 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;
+ }
+
+ if (!EnsureBeginTransaction()) {
+ return nullptr;
+ }
+
+ // Shmem is only valid if the surface is the latest snapshot (not detached).
+ if (!aDetached) {
+ // If there is a shmem associated with this snapshot id, then we want to try
+ // use that directly without having to allocate a new shmem for retrieval.
+ auto it = mTextureInfo.find(aTextureId);
+ if (it != mTextureInfo.end() && it->second.mSnapshotShmem) {
+ const auto shmemPtr =
+ reinterpret_cast<uint8_t*>(it->second.mSnapshotShmem->memory());
+ MOZ_ASSERT(shmemPtr);
+ mRecorder->RecordEvent(RecordedPrepareShmem(aTextureId));
+ auto checkpoint = CreateCheckpoint();
+ if (NS_WARN_IF(!mRecorder->WaitForCheckpoint(checkpoint))) {
+ return nullptr;
+ }
+ gfx::IntSize size = aSurface->GetSize();
+ gfx::SurfaceFormat format = aSurface->GetFormat();
+ auto stride = ImageDataSerializer::ComputeRGBStride(format, size.width);
+ RefPtr<gfx::DataSourceSurface> dataSurface =
+ gfx::Factory::CreateWrappingDataSourceSurface(shmemPtr, stride, size,
+ format);
+ return dataSurface.forget();
+ }
+ }
+
+ RecordEvent(RecordedPrepareDataForSurface(aSurface));
+
+ gfx::IntSize ssSize = aSurface->GetSize();
+ gfx::SurfaceFormat ssFormat = aSurface->GetFormat();
+ if (!EnsureDataSurfaceShmem(ssSize, ssFormat)) {
+ return nullptr;
+ }
+
+ RecordEvent(RecordedGetDataForSurface(aSurface));
+ auto checkpoint = CreateCheckpoint();
+ if (NS_WARN_IF(!mRecorder->WaitForCheckpoint(checkpoint))) {
+ return nullptr;
+ }
+
+ mDataSurfaceShmemAvailable = false;
+ struct DataShmemHolder {
+ RefPtr<ipc::SharedMemoryBasic> shmem;
+ RefPtr<CanvasChild> canvasChild;
+ };
+
+ auto* data = static_cast<uint8_t*>(mDataSurfaceShmem->memory());
+ auto* closure = new DataShmemHolder{do_AddRef(mDataSurfaceShmem), this};
+ auto stride = ImageDataSerializer::ComputeRGBStride(ssFormat, ssSize.width);
+
+ RefPtr<gfx::DataSourceSurface> dataSurface =
+ gfx::Factory::CreateWrappingDataSourceSurface(
+ data, stride, ssSize, ssFormat, ReleaseDataShmemHolder, closure);
+ return dataSurface.forget();
+}
+
+/* static */ void CanvasChild::ReleaseDataShmemHolder(void* aClosure) {
+ if (!NS_IsMainThread()) {
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "CanvasChild::ReleaseDataShmemHolder",
+ [aClosure]() { ReleaseDataShmemHolder(aClosure); }));
+ return;
+ }
+
+ auto* shmemHolder = static_cast<DataShmemHolder*>(aClosure);
+ shmemHolder->canvasChild->ReturnDataSurfaceShmem(shmemHolder->shmem.forget());
+ delete shmemHolder;
+}
+
+already_AddRefed<gfx::SourceSurface> CanvasChild::WrapSurface(
+ const RefPtr<gfx::SourceSurface>& aSurface, int64_t aTextureId) {
+ NS_ASSERT_OWNINGTHREAD(CanvasChild);
+
+ if (!aSurface) {
+ return nullptr;
+ }
+
+ return MakeAndAddRef<SourceSurfaceCanvasRecording>(aTextureId, aSurface, this,
+ mRecorder);
+}
+
+void CanvasChild::ReturnDataSurfaceShmem(
+ already_AddRefed<ipc::SharedMemoryBasic> aDataSurfaceShmem) {
+ RefPtr<ipc::SharedMemoryBasic> data = aDataSurfaceShmem;
+ // We can only reuse the latest data surface shmem.
+ if (data == mDataSurfaceShmem) {
+ MOZ_ASSERT(!mDataSurfaceShmemAvailable);
+ mDataSurfaceShmemAvailable = true;
+ }
+}
+
+void CanvasChild::AttachSurface(const RefPtr<gfx::SourceSurface>& aSurface) {
+ if (auto* surface =
+ static_cast<SourceSurfaceCanvasRecording*>(aSurface.get())) {
+ surface->AttachSurface();
+ }
+}
+
+void CanvasChild::DetachSurface(const RefPtr<gfx::SourceSurface>& aSurface) {
+ if (auto* surface =
+ static_cast<SourceSurfaceCanvasRecording*>(aSurface.get())) {
+ surface->DetachSurface();
+ }
+}
+
+ipc::IPCResult CanvasChild::RecvNotifyRequiresRefresh(int64_t aTextureId) {
+ auto it = mTextureInfo.find(aTextureId);
+ if (it != mTextureInfo.end()) {
+ it->second.mRequiresRefresh = true;
+ }
+ return IPC_OK();
+}
+
+bool CanvasChild::RequiresRefresh(int64_t aTextureId) const {
+ if (mBlocked) {
+ return true;
+ }
+ auto it = mTextureInfo.find(aTextureId);
+ if (it != mTextureInfo.end()) {
+ return it->second.mRequiresRefresh;
+ }
+ return false;
+}
+
+ipc::IPCResult CanvasChild::RecvSnapshotShmem(
+ int64_t aTextureId, Handle&& aShmemHandle, uint32_t aShmemSize,
+ SnapshotShmemResolver&& aResolve) {
+ auto it = mTextureInfo.find(aTextureId);
+ if (it != mTextureInfo.end()) {
+ auto shmem = MakeRefPtr<ipc::SharedMemoryBasic>();
+ if (NS_WARN_IF(!shmem->SetHandle(std::move(aShmemHandle),
+ ipc::SharedMemory::RightsReadOnly)) ||
+ NS_WARN_IF(!shmem->Map(aShmemSize))) {
+ shmem = nullptr;
+ } else {
+ it->second.mSnapshotShmem = std::move(shmem);
+ }
+ aResolve(true);
+ } else {
+ aResolve(false);
+ }
+ return IPC_OK();
+}
+
+void CanvasChild::CleanupTexture(int64_t aTextureId) {
+ mTextureInfo.erase(aTextureId);
+}
+
+} // namespace layers
+} // namespace mozilla