fune/gfx/layers/ipc/CanvasChild.cpp

379 lines
12 KiB
C++

/* -*- 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 "nsIObserverService.h"
#include "RecordedCanvasEventImpl.h"
namespace mozilla {
namespace layers {
/* static */ bool CanvasChild::mInForeground = true;
class RingBufferWriterServices final
: public CanvasEventRingBuffer::WriterServices {
public:
explicit RingBufferWriterServices(RefPtr<CanvasChild> aCanvasChild)
: mCanvasChild(aCanvasChild) {}
~RingBufferWriterServices() override = default;
bool ReaderClosed() override {
if (!mCanvasChild) {
return false;
}
return !mCanvasChild->CanSend() || ipc::ProcessChild::ExpectingShutdown();
}
void ResumeReader() override {
if (!mCanvasChild) {
return;
}
mCanvasChild->ResumeTranslation();
}
private:
const WeakPtr<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() = 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() {
NotifyCanvasDeviceReset();
mRecorder->RecordEvent(RecordedDeviceChangeAcknowledged());
return IPC_OK();
}
/* static */ bool CanvasChild::mDeactivated = false;
ipc::IPCResult CanvasChild::RecvDeactivate() {
RefPtr<CanvasChild> self(this);
mDeactivated = true;
if (auto* cm = gfx::CanvasManagerChild::Get()) {
cm->DeactivateCanvas();
}
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),
/* aUseIPDLThread */ false);
}
}
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.
if (mRecorder) {
mRecorder->DetachResources();
mRecorder = nullptr;
}
}
void CanvasChild::ResumeTranslation() {
if (CanSend()) {
SendResumeTranslation();
}
}
void CanvasChild::Destroy() {
if (CanSend()) {
Send__delete__(this);
}
}
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;
}
// We're forwarding textures, so we must be in the foreground.
mInForeground = true;
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);
}
bool CanvasChild::EnsureBeginTransaction() {
// We drop mRecorder in ActorDestroy to break the reference cycle.
if (!mRecorder) {
return false;
}
if (!mIsInTransaction) {
// Ensure we are using a large buffer when in the foreground and small one
// in the background.
if (mInForeground != mRecorder->UsingLargeStream()) {
SharedMemoryBasic::Handle handle;
if (!mRecorder->SwitchBuffer(OtherPid(), &handle) ||
!SendNewBuffer(std::move(handle))) {
mRecorder = nullptr;
return false;
}
}
mRecorder->RecordEvent(RecordedCanvasBeginTransaction());
mIsInTransaction = true;
}
return true;
}
void CanvasChild::EndTransaction() {
// We drop mRecorder in ActorDestroy to break the reference cycle.
if (!mRecorder) {
return;
}
if (mIsInTransaction) {
mRecorder->RecordEvent(RecordedCanvasEndTransaction());
mIsInTransaction = false;
}
++mTransactionsSinceGetDataSurface;
}
/* static */
void CanvasChild::ClearCachedResources() {
// We use this as a proxy for the tab being in the backgound.
mInForeground = false;
}
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.
return !mRecorder || mRecorder->hasOneRef();
}
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;
}
if (!EnsureBeginTransaction()) {
return nullptr;
}
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