Backed out changeset 2b56c2b2837a (bug 1863914) for causing bustage on CanvasDrawEventRecorder.h. CLOSED TREE

This commit is contained in:
Natalia Csoregi 2023-11-22 16:31:15 +02:00
parent 17befdb121
commit 00e5d5a924
19 changed files with 1125 additions and 1012 deletions

View file

@ -19,6 +19,26 @@ InlineTranslator::InlineTranslator(DrawTarget* aDT, void* aFontContext)
: mBaseDT(aDT), mFontContext(aFontContext) {} : mBaseDT(aDT), mFontContext(aFontContext) {}
bool InlineTranslator::TranslateRecording(char* aData, size_t aLen) { bool InlineTranslator::TranslateRecording(char* aData, size_t aLen) {
// an istream like class for reading from memory
struct MemReader {
MemReader(char* aData, size_t aLen) : mData(aData), mEnd(aData + aLen) {}
void read(char* s, std::streamsize n) {
if (n <= (mEnd - mData)) {
memcpy(s, mData, n);
mData += n;
} else {
// We've requested more data than is available
// set the Reader into an eof state
SetIsBad();
}
}
bool eof() { return mData > mEnd; }
bool good() { return !eof(); }
void SetIsBad() { mData = mEnd + 1; }
char* mData;
char* mEnd;
};
MemReader reader(aData, aLen); MemReader reader(aData, aLen);
uint32_t magicInt; uint32_t magicInt;

View file

@ -166,28 +166,6 @@ class InlineTranslator : public Translator {
std::string GetError() { return mError; } std::string GetError() { return mError; }
protected: protected:
// an istream like class for reading from memory
struct MemReader {
constexpr MemReader(char* aData, size_t aLen)
: mData(aData), mEnd(aData + aLen) {}
void read(char* s, std::streamsize n) {
if (n <= (mEnd - mData)) {
memcpy(s, mData, n);
mData += n;
} else {
// We've requested more data than is available
// set the Reader into an eof state
SetIsBad();
}
}
bool eof() { return mData > mEnd; }
bool good() { return !eof(); }
void SetIsBad() { mData = mEnd + 1; }
char* mData;
char* mEnd;
};
RefPtr<DrawTarget> mBaseDT; RefPtr<DrawTarget> mBaseDT;
Matrix mBaseDTTransform; Matrix mBaseDTTransform;
nsRefPtrHashtable<nsPtrHashKey<void>, DrawTarget> mDrawTargets; nsRefPtrHashtable<nsPtrHashKey<void>, DrawTarget> mDrawTargets;

View file

@ -25,6 +25,13 @@ bool RecordedEvent::DoWithEventFromStream(
return DoWithEvent(aStream, aType, aAction); return DoWithEvent(aStream, aType, aAction);
} }
/* static */
bool RecordedEvent::DoWithEventFromStream(
EventRingBuffer& aStream, EventType aType,
const std::function<bool(RecordedEvent*)>& aAction) {
return DoWithEvent(aStream, aType, aAction);
}
std::string RecordedEvent::GetEventName(EventType aType) { std::string RecordedEvent::GetEventName(EventType aType) {
switch (aType) { switch (aType) {
case DRAWTARGETCREATION: case DRAWTARGETCREATION:

View file

@ -214,7 +214,7 @@ struct SizeCollector {
}; };
struct MemWriter { struct MemWriter {
constexpr explicit MemWriter(char* aPtr) : mPtr(aPtr) {} explicit MemWriter(char* aPtr) : mPtr(aPtr) {}
void write(const char* aData, size_t aSize) { void write(const char* aData, size_t aSize) {
memcpy(mPtr, aData, aSize); memcpy(mPtr, aData, aSize);
mPtr += aSize; mPtr += aSize;
@ -222,30 +222,13 @@ struct MemWriter {
char* mPtr; char* mPtr;
}; };
class ContiguousBuffer { // This is a simple interface for an EventRingBuffer, so we can use it in the
public: // RecordedEvent reading and writing machinery.
ContiguousBuffer(char* aStart, size_t aSize) class EventRingBuffer {
: mWriter(aStart), mEnd(aStart + aSize) {}
constexpr MOZ_IMPLICIT ContiguousBuffer(std::nullptr_t) : mWriter(nullptr) {}
MemWriter& Writer() { return mWriter; }
size_t SizeRemaining() { return mWriter.mPtr ? mEnd - mWriter.mPtr : 0; }
bool IsValid() { return !!mWriter.mPtr; }
private:
MemWriter mWriter;
char* mEnd = nullptr;
};
// Allows a derived class to provide guaranteed contiguous buffer.
class ContiguousBufferStream {
public: public:
/** /**
* Templated RecordEvent function so that we can record into the buffer * Templated RecordEvent function so that when we have enough contiguous
* quickly using MemWriter. * space we can record into the buffer quickly using MemWriter.
* *
* @param aRecordedEvent the event to record * @param aRecordedEvent the event to record
*/ */
@ -254,25 +237,56 @@ class ContiguousBufferStream {
SizeCollector size; SizeCollector size;
WriteElement(size, aRecordedEvent->GetType()); WriteElement(size, aRecordedEvent->GetType());
aRecordedEvent->Record(size); aRecordedEvent->Record(size);
auto& buffer = GetContiguousBuffer(size.mTotalSize); if (size.mTotalSize > mAvailable) {
if (!buffer.IsValid()) { WaitForAndRecalculateAvailableSpace();
return; }
if (size.mTotalSize <= mAvailable) {
MemWriter writer(mBufPos);
WriteElement(writer, aRecordedEvent->GetType());
aRecordedEvent->Record(writer);
UpdateWriteTotalsBy(size.mTotalSize);
} else {
WriteElement(*this, aRecordedEvent->GetType());
aRecordedEvent->Record(*this);
} }
MOZ_ASSERT(size.mTotalSize <= buffer.SizeRemaining());
WriteElement(buffer.Writer(), aRecordedEvent->GetType());
aRecordedEvent->Record(buffer.Writer());
IncrementEventCount();
} }
/**
* Simple write function required by WriteElement.
*
* @param aData the data to be written to the buffer
* @param aSize the number of chars to write
*/
virtual void write(const char* const aData, const size_t aSize) = 0;
/**
* Simple read function required by ReadElement.
*
* @param aOut the pointer to read into
* @param aSize the number of chars to read
*/
virtual void read(char* const aOut, const size_t aSize) = 0;
virtual bool good() const = 0;
virtual void SetIsBad() = 0;
protected: protected:
/** /**
* Provide a contiguous buffer with at least aSize remaining. * Wait until space is available for writing and then set mBufPos and
* mAvailable.
*/ */
virtual ContiguousBuffer& GetContiguousBuffer(size_t aSize) = 0; virtual bool WaitForAndRecalculateAvailableSpace() = 0;
virtual void IncrementEventCount() = 0; /**
* Update write count, mBufPos and mAvailable.
*
* @param aCount number of bytes written
*/
virtual void UpdateWriteTotalsBy(uint32_t aCount) = 0;
char* mBufPos = nullptr;
uint32_t mAvailable = 0;
}; };
struct MemStream { struct MemStream {
@ -416,7 +430,7 @@ class RecordedEvent {
virtual void RecordToStream(std::ostream& aStream) const = 0; virtual void RecordToStream(std::ostream& aStream) const = 0;
virtual void RecordToStream(EventStream& aStream) const = 0; virtual void RecordToStream(EventStream& aStream) const = 0;
virtual void RecordToStream(ContiguousBufferStream& aStream) const = 0; virtual void RecordToStream(EventRingBuffer& aStream) const = 0;
virtual void RecordToStream(MemStream& aStream) const = 0; virtual void RecordToStream(MemStream& aStream) const = 0;
virtual void OutputSimpleEventInfo(std::stringstream& aStringStream) const {} virtual void OutputSimpleEventInfo(std::stringstream& aStringStream) const {}
@ -446,6 +460,9 @@ class RecordedEvent {
static bool DoWithEventFromStream( static bool DoWithEventFromStream(
EventStream& aStream, EventType aType, EventStream& aStream, EventType aType,
const std::function<bool(RecordedEvent*)>& aAction); const std::function<bool(RecordedEvent*)>& aAction);
static bool DoWithEventFromStream(
EventRingBuffer& aStream, EventType aType,
const std::function<bool(RecordedEvent*)>& aAction);
EventType GetType() const { return (EventType)mType; } EventType GetType() const { return (EventType)mType; }
@ -478,7 +495,7 @@ class RecordedEventDerived : public RecordedEvent {
WriteElement(aStream, this->mType); WriteElement(aStream, this->mType);
static_cast<const Derived*>(this)->Record(aStream); static_cast<const Derived*>(this)->Record(aStream);
} }
void RecordToStream(ContiguousBufferStream& aStream) const final { void RecordToStream(EventRingBuffer& aStream) const final {
aStream.RecordEvent(static_cast<const Derived*>(this)); aStream.RecordEvent(static_cast<const Derived*>(this));
} }
void RecordToStream(MemStream& aStream) const override { void RecordToStream(MemStream& aStream) const override {

View file

@ -151,12 +151,6 @@ void CanvasManagerChild::EndCanvasTransaction() {
} }
} }
void CanvasManagerChild::ClearCachedResources() {
if (mCanvasChild) {
mCanvasChild->ClearCachedResources();
}
}
void CanvasManagerChild::DeactivateCanvas() { void CanvasManagerChild::DeactivateCanvas() {
mActive = false; mActive = false;
if (mCanvasChild) { if (mCanvasChild) {

View file

@ -47,7 +47,6 @@ class CanvasManagerChild final : public PCanvasManagerChild {
bool IsCanvasActive() { return mActive; } bool IsCanvasActive() { return mActive; }
void EndCanvasTransaction(); void EndCanvasTransaction();
void ClearCachedResources();
void DeactivateCanvas(); void DeactivateCanvas();
RefPtr<layers::CanvasChild> GetCanvasChild(); RefPtr<layers::CanvasChild> GetCanvasChild();

View file

@ -9,268 +9,294 @@
#include <string.h> #include <string.h>
#include "mozilla/layers/SharedSurfacesChild.h" #include "mozilla/layers/SharedSurfacesChild.h"
#include "mozilla/StaticPrefs_gfx.h" #include "nsThreadUtils.h"
#include "RecordedCanvasEventImpl.h"
namespace mozilla { namespace mozilla {
namespace layers { namespace layers {
struct ShmemAndHandle { static const uint32_t kMaxSpinCount = 200;
RefPtr<ipc::SharedMemoryBasic> shmem;
Handle handle;
};
static Maybe<ShmemAndHandle> CreateAndMapShmem(size_t aSize) { static const TimeDuration kTimeout = TimeDuration::FromMilliseconds(100);
auto shmem = MakeRefPtr<ipc::SharedMemoryBasic>(); static const int32_t kTimeoutRetryCount = 50;
if (!shmem->Create(aSize) || !shmem->Map(aSize)) {
return Nothing();
}
auto shmemHandle = shmem->TakeHandle(); static const uint32_t kCacheLineSize = 64;
if (!shmemHandle) { static const uint32_t kSmallStreamSize = 64 * 1024;
return Nothing(); static const uint32_t kLargeStreamSize = 2048 * 1024;
}
return Some(ShmemAndHandle{shmem.forget(), std::move(shmemHandle)}); static_assert((static_cast<uint64_t>(UINT32_MAX) + 1) % kSmallStreamSize == 0,
"kSmallStreamSize must be a power of two.");
static_assert((static_cast<uint64_t>(UINT32_MAX) + 1) % kLargeStreamSize == 0,
"kLargeStreamSize must be a power of two.");
uint32_t CanvasEventRingBuffer::StreamSize() {
return mLargeStream ? kLargeStreamSize : kSmallStreamSize;
} }
CanvasDrawEventRecorder::CanvasDrawEventRecorder() { bool CanvasEventRingBuffer::InitBuffer(
mDefaultBufferSize = ipc::SharedMemory::PageAlignedSize( base::ProcessId aOtherPid, ipc::SharedMemoryBasic::Handle* aReadHandle) {
StaticPrefs::gfx_canvas_remote_default_buffer_size()); size_t shmemSize = StreamSize() + (2 * kCacheLineSize);
mMaxSpinCount = StaticPrefs::gfx_canvas_remote_max_spin_count(); mSharedMemory = MakeAndAddRef<ipc::SharedMemoryBasic>();
mDropBufferLimit = StaticPrefs::gfx_canvas_remote_drop_buffer_limit(); if (NS_WARN_IF(!mSharedMemory->Create(shmemSize)) ||
mDropBufferOnZero = mDropBufferLimit; NS_WARN_IF(!mSharedMemory->Map(shmemSize))) {
} mGood = false;
bool CanvasDrawEventRecorder::Init(TextureType aTextureType,
UniquePtr<Helpers> aHelpers) {
mHelpers = std::move(aHelpers);
MOZ_ASSERT(mTextureType == TextureType::Unknown);
auto header = CreateAndMapShmem(sizeof(Header));
if (NS_WARN_IF(header.isNothing())) {
return false; return false;
} }
mHeader = static_cast<Header*>(header->shmem->memory()); *aReadHandle = mSharedMemory->TakeHandle();
mHeader->eventCount = 0; if (NS_WARN_IF(!*aReadHandle)) {
mHeader->writerWaitCount = 0; mGood = false;
mHeader->writerState = State::Processing;
mHeader->processedCount = 0;
mHeader->readerState = State::Processing;
// We always keep at least two buffers. This means that when we
// have to add a new buffer, there is at least a full buffer that requires
// translating while the handle is sent over.
AutoTArray<Handle, 2> bufferHandles;
auto buffer = CreateAndMapShmem(mDefaultBufferSize);
if (NS_WARN_IF(buffer.isNothing())) {
return false;
}
mCurrentBuffer = CanvasBuffer(std::move(buffer->shmem));
bufferHandles.AppendElement(std::move(buffer->handle));
buffer = CreateAndMapShmem(mDefaultBufferSize);
if (NS_WARN_IF(buffer.isNothing())) {
return false;
}
mRecycledBuffers.emplace(buffer->shmem.forget(), 0);
bufferHandles.AppendElement(std::move(buffer->handle));
mWriterSemaphore.reset(CrossProcessSemaphore::Create("CanvasRecorder", 0));
auto writerSem = mWriterSemaphore->CloneHandle();
mWriterSemaphore->CloseHandle();
if (!IsHandleValid(writerSem)) {
return false; return false;
} }
mReaderSemaphore.reset(CrossProcessSemaphore::Create("CanvasTranslator", 0)); mBuf = static_cast<char*>(mSharedMemory->memory());
auto readerSem = mReaderSemaphore->CloneHandle(); mBufPos = mBuf;
mReaderSemaphore->CloseHandle(); mAvailable = StreamSize();
if (!IsHandleValid(readerSem)) {
return false;
}
if (!mHelpers->InitTranslator(aTextureType, std::move(header->handle), static_assert(sizeof(ReadFooter) <= kCacheLineSize,
std::move(bufferHandles), mDefaultBufferSize, "ReadFooter must fit in kCacheLineSize.");
std::move(readerSem), std::move(writerSem), mRead = reinterpret_cast<ReadFooter*>(mBuf + StreamSize());
/* aUseIPDLThread */ false)) { mRead->count = 0;
return false; mRead->returnCount = 0;
} mRead->state = State::Processing;
static_assert(sizeof(WriteFooter) <= kCacheLineSize,
"WriteFooter must fit in kCacheLineSize.");
mWrite = reinterpret_cast<WriteFooter*>(mBuf + StreamSize() + kCacheLineSize);
mWrite->count = 0;
mWrite->returnCount = 0;
mWrite->requiredDifference = 0;
mWrite->state = State::Processing;
mOurCount = 0;
mTextureType = aTextureType;
mHeaderShmem = header->shmem;
return true; return true;
} }
void CanvasDrawEventRecorder::RecordEvent(const gfx::RecordedEvent& aEvent) { bool CanvasEventRingBuffer::InitWriter(
aEvent.RecordToStream(*this); base::ProcessId aOtherPid, ipc::SharedMemoryBasic::Handle* aReadHandle,
CrossProcessSemaphoreHandle* aReaderSem,
CrossProcessSemaphoreHandle* aWriterSem,
UniquePtr<WriterServices> aWriterServices) {
if (!InitBuffer(aOtherPid, aReadHandle)) {
return false;
}
mReaderSemaphore.reset(
CrossProcessSemaphore::Create("SharedMemoryStreamParent", 0));
*aReaderSem = mReaderSemaphore->CloneHandle();
mReaderSemaphore->CloseHandle();
if (!IsHandleValid(*aReaderSem)) {
return false;
}
mWriterSemaphore.reset(
CrossProcessSemaphore::Create("SharedMemoryStreamChild", 0));
*aWriterSem = mWriterSemaphore->CloneHandle();
mWriterSemaphore->CloseHandle();
if (!IsHandleValid(*aWriterSem)) {
return false;
}
mWriterServices = std::move(aWriterServices);
mGood = true;
return true;
} }
int64_t CanvasDrawEventRecorder::CreateCheckpoint() { bool CanvasEventRingBuffer::InitReader(
int64_t checkpoint = mHeader->eventCount; ipc::SharedMemoryBasic::Handle aReadHandle,
RecordEvent(RecordedCheckpoint()); CrossProcessSemaphoreHandle aReaderSem,
return checkpoint; CrossProcessSemaphoreHandle aWriterSem,
UniquePtr<ReaderServices> aReaderServices) {
if (!SetNewBuffer(std::move(aReadHandle))) {
return false;
}
mReaderSemaphore.reset(CrossProcessSemaphore::Create(std::move(aReaderSem)));
mReaderSemaphore->CloseHandle();
mWriterSemaphore.reset(CrossProcessSemaphore::Create(std::move(aWriterSem)));
mWriterSemaphore->CloseHandle();
mReaderServices = std::move(aReaderServices);
mGood = true;
return true;
} }
bool CanvasDrawEventRecorder::WaitForCheckpoint(int64_t aCheckpoint) { bool CanvasEventRingBuffer::SetNewBuffer(
uint32_t spinCount = mMaxSpinCount; ipc::SharedMemoryBasic::Handle aReadHandle) {
MOZ_RELEASE_ASSERT(
!mSharedMemory,
"Shared memory should have been dropped before new buffer is sent.");
size_t shmemSize = StreamSize() + (2 * kCacheLineSize);
mSharedMemory = MakeAndAddRef<ipc::SharedMemoryBasic>();
if (NS_WARN_IF(!mSharedMemory->SetHandle(
std::move(aReadHandle), ipc::SharedMemory::RightsReadWrite)) ||
NS_WARN_IF(!mSharedMemory->Map(shmemSize))) {
mGood = false;
return false;
}
mSharedMemory->CloseHandle();
mBuf = static_cast<char*>(mSharedMemory->memory());
mRead = reinterpret_cast<ReadFooter*>(mBuf + StreamSize());
mWrite = reinterpret_cast<WriteFooter*>(mBuf + StreamSize() + kCacheLineSize);
mOurCount = 0;
return true;
}
bool CanvasEventRingBuffer::WaitForAndRecalculateAvailableSpace() {
if (!good()) {
return false;
}
uint32_t bufPos = mOurCount % StreamSize();
uint32_t maxToWrite = StreamSize() - bufPos;
mAvailable = std::min(maxToWrite, WaitForBytesToWrite());
if (!mAvailable) {
mBufPos = nullptr;
return false;
}
mBufPos = mBuf + bufPos;
return true;
}
void CanvasEventRingBuffer::write(const char* const aData, const size_t aSize) {
const char* curDestPtr = aData;
size_t remainingToWrite = aSize;
if (remainingToWrite > mAvailable) {
if (!WaitForAndRecalculateAvailableSpace()) {
return;
}
}
if (remainingToWrite <= mAvailable) {
memcpy(mBufPos, curDestPtr, remainingToWrite);
UpdateWriteTotalsBy(remainingToWrite);
return;
}
do { do {
if (mHeader->processedCount >= aCheckpoint) { memcpy(mBufPos, curDestPtr, mAvailable);
return true; IncrementWriteCountBy(mAvailable);
curDestPtr += mAvailable;
remainingToWrite -= mAvailable;
if (!WaitForAndRecalculateAvailableSpace()) {
return;
} }
} while (--spinCount != 0); } while (remainingToWrite > mAvailable);
mHeader->writerState = State::AboutToWait; memcpy(mBufPos, curDestPtr, remainingToWrite);
if (mHeader->processedCount >= aCheckpoint) { UpdateWriteTotalsBy(remainingToWrite);
mHeader->writerState = State::Processing; }
return true;
void CanvasEventRingBuffer::IncrementWriteCountBy(uint32_t aCount) {
mOurCount += aCount;
mWrite->count = mOurCount;
if (mRead->state != State::Processing) {
CheckAndSignalReader();
}
}
void CanvasEventRingBuffer::UpdateWriteTotalsBy(uint32_t aCount) {
IncrementWriteCountBy(aCount);
mBufPos += aCount;
mAvailable -= aCount;
}
bool CanvasEventRingBuffer::WaitForAndRecalculateAvailableData() {
if (!good()) {
return false;
} }
mHeader->writerWaitCount = aCheckpoint; uint32_t bufPos = mOurCount % StreamSize();
mHeader->writerState = State::Waiting; uint32_t maxToRead = StreamSize() - bufPos;
mAvailable = std::min(maxToRead, WaitForBytesToRead());
if (!mAvailable) {
SetIsBad();
mBufPos = nullptr;
return false;
}
// Wait unless we detect the reading side has closed. mBufPos = mBuf + bufPos;
while (!mHelpers->ReaderClosed() && mHeader->readerState != State::Failed) { return true;
if (mWriterSemaphore->Wait(Some(TimeDuration::FromMilliseconds(100)))) { }
MOZ_ASSERT(mHeader->processedCount >= aCheckpoint);
return true; void CanvasEventRingBuffer::read(char* const aOut, const size_t aSize) {
char* curSrcPtr = aOut;
size_t remainingToRead = aSize;
if (remainingToRead > mAvailable) {
if (!WaitForAndRecalculateAvailableData()) {
return;
} }
} }
// Either the reader has failed or we're stopping writing for some other if (remainingToRead <= mAvailable) {
// reason (e.g. shutdown), so mark us as failed so the reader is aware. memcpy(curSrcPtr, mBufPos, remainingToRead);
mHeader->writerState = State::Failed; UpdateReadTotalsBy(remainingToRead);
return false; return;
}
void CanvasDrawEventRecorder::WriteInternalEvent(EventType aEventType) {
MOZ_ASSERT(mCurrentBuffer.SizeRemaining() > 0);
WriteElement(mCurrentBuffer.Writer(), aEventType);
IncrementEventCount();
}
gfx::ContiguousBuffer& CanvasDrawEventRecorder::GetContiguousBuffer(
size_t aSize) {
// We make sure that our buffer can hold aSize + 1 to ensure we always have
// room for the end of buffer event.
// Check if there is enough room is our current buffer.
if (mCurrentBuffer.SizeRemaining() > aSize) {
return mCurrentBuffer;
} }
// If the next recycled buffer is big enough and free use that.
if (mRecycledBuffers.front().Capacity() > aSize &&
mRecycledBuffers.front().eventCount <= mHeader->processedCount) {
// Only queue default size buffers for recycling.
if (mCurrentBuffer.Capacity() == mDefaultBufferSize) {
WriteInternalEvent(RECYCLE_BUFFER);
mRecycledBuffers.emplace(std::move(mCurrentBuffer.shmem),
mHeader->eventCount);
} else {
WriteInternalEvent(DROP_BUFFER);
}
mCurrentBuffer = CanvasBuffer(std::move(mRecycledBuffers.front().shmem));
mRecycledBuffers.pop();
// If we have more than one recycled buffers free a configured number of
// times in a row then drop one.
if (mRecycledBuffers.size() > 1 &&
mRecycledBuffers.front().eventCount < mHeader->processedCount) {
if (--mDropBufferOnZero == 0) {
WriteInternalEvent(DROP_BUFFER);
mCurrentBuffer =
CanvasBuffer(std::move(mRecycledBuffers.front().shmem));
mRecycledBuffers.pop();
mDropBufferOnZero = 1;
}
} else {
mDropBufferOnZero = mDropBufferLimit;
}
return mCurrentBuffer;
}
// We don't have a buffer free or it is not big enough, so create a new one.
WriteInternalEvent(PAUSE_TRANSLATION);
// Only queue default size buffers for recycling.
if (mCurrentBuffer.Capacity() == mDefaultBufferSize) {
mRecycledBuffers.emplace(std::move(mCurrentBuffer.shmem),
mHeader->eventCount);
}
size_t bufferSize = std::max(mDefaultBufferSize,
ipc::SharedMemory::PageAlignedSize(aSize + 1));
auto newBuffer = CreateAndMapShmem(bufferSize);
if (NS_WARN_IF(newBuffer.isNothing())) {
mHeader->writerState = State::Failed;
mCurrentBuffer = CanvasBuffer(nullptr);
return mCurrentBuffer;
}
if (!mHelpers->AddBuffer(std::move(newBuffer->handle), bufferSize)) {
mHeader->writerState = State::Failed;
mCurrentBuffer = CanvasBuffer(nullptr);
return mCurrentBuffer;
}
mCurrentBuffer = CanvasBuffer(std::move(newBuffer->shmem));
return mCurrentBuffer;
}
void CanvasDrawEventRecorder::DropFreeBuffers() {
while (mRecycledBuffers.size() > 1 &&
mRecycledBuffers.front().eventCount < mHeader->processedCount) {
WriteInternalEvent(DROP_BUFFER);
mCurrentBuffer = CanvasBuffer(std::move(mRecycledBuffers.front().shmem));
mRecycledBuffers.pop();
}
}
void CanvasDrawEventRecorder::IncrementEventCount() {
mHeader->eventCount++;
CheckAndSignalReader();
}
void CanvasDrawEventRecorder::CheckAndSignalReader() {
do { do {
switch (mHeader->readerState) { memcpy(curSrcPtr, mBufPos, mAvailable);
IncrementReadCountBy(mAvailable);
curSrcPtr += mAvailable;
remainingToRead -= mAvailable;
if (!WaitForAndRecalculateAvailableData()) {
return;
}
} while (remainingToRead > mAvailable);
memcpy(curSrcPtr, mBufPos, remainingToRead);
UpdateReadTotalsBy(remainingToRead);
}
void CanvasEventRingBuffer::IncrementReadCountBy(uint32_t aCount) {
mOurCount += aCount;
mRead->count = mOurCount;
if (mWrite->state != State::Processing) {
CheckAndSignalWriter();
}
}
void CanvasEventRingBuffer::UpdateReadTotalsBy(uint32_t aCount) {
IncrementReadCountBy(aCount);
mBufPos += aCount;
mAvailable -= aCount;
}
void CanvasEventRingBuffer::CheckAndSignalReader() {
do {
switch (mRead->state) {
case State::Processing: case State::Processing:
case State::Paused:
case State::Failed: case State::Failed:
return; return;
case State::AboutToWait: case State::AboutToWait:
// The reader is making a decision about whether to wait. So, we must // The reader is making a decision about whether to wait. So, we must
// wait until it has decided to avoid races. Check if the reader is // wait until it has decided to avoid races. Check if the reader is
// closed to avoid hangs. // closed to avoid hangs.
if (mHelpers->ReaderClosed()) { if (mWriterServices->ReaderClosed()) {
return; return;
} }
continue; continue;
case State::Waiting: case State::Waiting:
if (mHeader->processedCount < mHeader->eventCount) { if (mRead->count != mOurCount) {
// We have to use compareExchange here because the reader can change // We have to use compareExchange here because the reader can change
// from Waiting to Stopped. // from Waiting to Stopped.
if (mHeader->readerState.compareExchange(State::Waiting, if (mRead->state.compareExchange(State::Waiting, State::Processing)) {
State::Processing)) {
mReaderSemaphore->Signal(); mReaderSemaphore->Signal();
return; return;
} }
MOZ_ASSERT(mHeader->readerState == State::Stopped); MOZ_ASSERT(mRead->state == State::Stopped);
continue; continue;
} }
return; return;
case State::Stopped: case State::Stopped:
if (mHeader->processedCount < mHeader->eventCount) { if (mRead->count != mOurCount) {
mHeader->readerState = State::Processing; mRead->state = State::Processing;
if (!mHelpers->RestartReader()) { mWriterServices->ResumeReader();
mHeader->writerState = State::Failed;
}
} }
return; return;
default: default:
@ -280,6 +306,267 @@ void CanvasDrawEventRecorder::CheckAndSignalReader() {
} while (true); } while (true);
} }
bool CanvasEventRingBuffer::HasDataToRead() {
return (mWrite->count != mOurCount);
}
bool CanvasEventRingBuffer::StopIfEmpty() {
// Double-check that the writer isn't waiting.
CheckAndSignalWriter();
mRead->state = State::AboutToWait;
if (HasDataToRead()) {
mRead->state = State::Processing;
return false;
}
mRead->state = State::Stopped;
return true;
}
bool CanvasEventRingBuffer::WaitForDataToRead(TimeDuration aTimeout,
int32_t aRetryCount) {
uint32_t spinCount = kMaxSpinCount;
do {
if (HasDataToRead()) {
return true;
}
} while (--spinCount != 0);
// Double-check that the writer isn't waiting.
CheckAndSignalWriter();
mRead->state = State::AboutToWait;
if (HasDataToRead()) {
mRead->state = State::Processing;
return true;
}
mRead->state = State::Waiting;
do {
if (mReaderSemaphore->Wait(Some(aTimeout))) {
MOZ_RELEASE_ASSERT(HasDataToRead());
return true;
}
if (mReaderServices->WriterClosed()) {
// Something has gone wrong on the writing side, just return false so
// that we can hopefully recover.
return false;
}
} while (aRetryCount-- > 0);
// We have to use compareExchange here because the writer can change our
// state if we are waiting. signaled
if (!mRead->state.compareExchange(State::Waiting, State::Stopped)) {
MOZ_RELEASE_ASSERT(HasDataToRead());
MOZ_RELEASE_ASSERT(mRead->state == State::Processing);
// The writer has just signaled us, so consume it before returning
MOZ_ALWAYS_TRUE(mReaderSemaphore->Wait());
return true;
}
return false;
}
uint8_t CanvasEventRingBuffer::ReadNextEvent() {
uint8_t nextEvent;
ReadElement(*this, nextEvent);
while (nextEvent == kCheckpointEventType && good()) {
ReadElement(*this, nextEvent);
}
if (nextEvent == kDropBufferEventType) {
// Writer is switching to a different sized buffer.
mBuf = nullptr;
mBufPos = nullptr;
mRead = nullptr;
mWrite = nullptr;
mAvailable = 0;
mSharedMemory = nullptr;
// We always toggle between smaller and larger stream sizes.
mLargeStream = !mLargeStream;
}
return nextEvent;
}
uint32_t CanvasEventRingBuffer::CreateCheckpoint() {
WriteElement(*this, kCheckpointEventType);
return mOurCount;
}
bool CanvasEventRingBuffer::WaitForCheckpoint(uint32_t aCheckpoint) {
return WaitForReadCount(aCheckpoint, kTimeout);
}
bool CanvasEventRingBuffer::SwitchBuffer(
base::ProcessId aOtherPid, ipc::SharedMemoryBasic::Handle* aReadHandle) {
WriteElement(*this, kDropBufferEventType);
// Make sure the drop buffer event has been read before continuing. We can't
// write an actual checkpoint because there will be no buffer to read from.
if (!WaitForCheckpoint(mOurCount)) {
return false;
}
mBuf = nullptr;
mBufPos = nullptr;
mRead = nullptr;
mWrite = nullptr;
mAvailable = 0;
mSharedMemory = nullptr;
// We always toggle between smaller and larger stream sizes.
mLargeStream = !mLargeStream;
return InitBuffer(aOtherPid, aReadHandle);
}
void CanvasEventRingBuffer::CheckAndSignalWriter() {
do {
switch (mWrite->state) {
case State::Processing:
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 (mReaderServices->WriterClosed()) {
return;
}
continue;
case State::Waiting:
if (mWrite->count - mOurCount <= mWrite->requiredDifference) {
mWrite->state = State::Processing;
mWriterSemaphore->Signal();
}
return;
default:
MOZ_ASSERT_UNREACHABLE("Invalid waiting state.");
return;
}
} while (true);
}
bool CanvasEventRingBuffer::WaitForReadCount(uint32_t aReadCount,
TimeDuration aTimeout) {
uint32_t requiredDifference = mOurCount - aReadCount;
uint32_t spinCount = kMaxSpinCount;
do {
if (mOurCount - mRead->count <= requiredDifference) {
return true;
}
} while (--spinCount != 0);
// Double-check that the reader isn't waiting.
CheckAndSignalReader();
mWrite->state = State::AboutToWait;
if (mOurCount - mRead->count <= requiredDifference) {
mWrite->state = State::Processing;
return true;
}
mWrite->requiredDifference = requiredDifference;
mWrite->state = State::Waiting;
// Wait unless we detect the reading side has closed.
while (!mWriterServices->ReaderClosed() && mRead->state != State::Failed) {
if (mWriterSemaphore->Wait(Some(aTimeout))) {
MOZ_ASSERT(mOurCount - mRead->count <= requiredDifference);
return true;
}
}
// Either the reader has failed or we're stopping writing for some other
// reason (e.g. shutdown), so mark us as failed so the reader is aware.
mWrite->state = State::Failed;
mGood = false;
return false;
}
uint32_t CanvasEventRingBuffer::WaitForBytesToWrite() {
uint32_t streamFullReadCount = mOurCount - StreamSize();
if (!WaitForReadCount(streamFullReadCount + 1, kTimeout)) {
return 0;
}
return mRead->count - streamFullReadCount;
}
uint32_t CanvasEventRingBuffer::WaitForBytesToRead() {
if (!WaitForDataToRead(kTimeout, kTimeoutRetryCount)) {
return 0;
}
return mWrite->count - mOurCount;
}
void CanvasEventRingBuffer::ReturnWrite(const char* aData, size_t aSize) {
uint32_t writeCount = mRead->returnCount;
uint32_t bufPos = writeCount % StreamSize();
uint32_t bufRemaining = StreamSize() - bufPos;
uint32_t availableToWrite =
std::min(bufRemaining, (mWrite->returnCount + StreamSize() - writeCount));
while (availableToWrite < aSize) {
if (availableToWrite) {
memcpy(mBuf + bufPos, aData, availableToWrite);
writeCount += availableToWrite;
mRead->returnCount = writeCount;
bufPos = writeCount % StreamSize();
bufRemaining = StreamSize() - bufPos;
aData += availableToWrite;
aSize -= availableToWrite;
} else if (mReaderServices->WriterClosed()) {
return;
}
availableToWrite = std::min(
bufRemaining, (mWrite->returnCount + StreamSize() - writeCount));
}
memcpy(mBuf + bufPos, aData, aSize);
writeCount += aSize;
mRead->returnCount = writeCount;
}
void CanvasEventRingBuffer::ReturnRead(char* aOut, size_t aSize) {
// First wait for the event returning the data to be read.
WaitForCheckpoint(mOurCount);
uint32_t readCount = mWrite->returnCount;
// If the event sending back data fails to play then it will ReturnWrite
// nothing. So, wait until something has been written or the reader has
// stopped processing.
while (readCount == mRead->returnCount) {
// We recheck the count, because the other side can write all the data and
// started waiting in between these two lines.
if (mRead->state != State::Processing && readCount == mRead->returnCount) {
return;
}
}
uint32_t bufPos = readCount % StreamSize();
uint32_t bufRemaining = StreamSize() - bufPos;
uint32_t availableToRead =
std::min(bufRemaining, (mRead->returnCount - readCount));
while (availableToRead < aSize) {
if (availableToRead) {
memcpy(aOut, mBuf + bufPos, availableToRead);
readCount += availableToRead;
mWrite->returnCount = readCount;
bufPos = readCount % StreamSize();
bufRemaining = StreamSize() - bufPos;
aOut += availableToRead;
aSize -= availableToRead;
} else if (mWriterServices->ReaderClosed()) {
return;
}
availableToRead = std::min(bufRemaining, (mRead->returnCount - readCount));
}
memcpy(aOut, mBuf + bufPos, aSize);
readCount += aSize;
mWrite->returnCount = readCount;
}
void CanvasDrawEventRecorder::StoreSourceSurfaceRecording( void CanvasDrawEventRecorder::StoreSourceSurfaceRecording(
gfx::SourceSurface* aSurface, const char* aReason) { gfx::SourceSurface* aSurface, const char* aReason) {
wr::ExternalImageId extId{}; wr::ExternalImageId extId{};

View file

@ -7,32 +7,195 @@
#ifndef mozilla_layers_CanvasDrawEventRecorder_h #ifndef mozilla_layers_CanvasDrawEventRecorder_h
#define mozilla_layers_CanvasDrawEventRecorder_h #define mozilla_layers_CanvasDrawEventRecorder_h
#include <queue>
#include "mozilla/Atomics.h"
#include "mozilla/gfx/DrawEventRecorder.h" #include "mozilla/gfx/DrawEventRecorder.h"
#include "mozilla/ipc/CrossProcessSemaphore.h" #include "mozilla/ipc/CrossProcessSemaphore.h"
#include "mozilla/ipc/SharedMemoryBasic.h" #include "mozilla/ipc/SharedMemoryBasic.h"
#include "mozilla/layers/LayersTypes.h"
#include "mozilla/RefPtr.h"
#include "mozilla/UniquePtr.h"
namespace mozilla { namespace mozilla {
using EventType = gfx::RecordedEvent::EventType;
namespace layers { namespace layers {
typedef mozilla::ipc::SharedMemoryBasic::Handle Handle; static const uint8_t kCheckpointEventType = -1;
typedef mozilla::CrossProcessSemaphoreHandle CrossProcessSemaphoreHandle; static const uint8_t kDropBufferEventType = -2;
class CanvasDrawEventRecorder final : public gfx::DrawEventRecorderPrivate, class CanvasEventRingBuffer final : public gfx::EventRingBuffer {
public gfx::ContiguousBufferStream {
public: public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(CanvasDrawEventRecorder, final) /**
* WriterServices allows consumers of CanvasEventRingBuffer to provide
* functions required by the write side of a CanvasEventRingBuffer without
* introducing unnecessary dependencies on IPC code.
*/
class WriterServices {
public:
virtual ~WriterServices() = default;
CanvasDrawEventRecorder(); /**
* @returns true if the reader of the CanvasEventRingBuffer has permanently
* stopped processing, otherwise returns false.
*/
virtual bool ReaderClosed() = 0;
/**
* Causes the reader to resume processing when it is in a stopped state.
*/
virtual void ResumeReader() = 0;
};
/**
* ReaderServices allows consumers of CanvasEventRingBuffer to provide
* functions required by the read side of a CanvasEventRingBuffer without
* introducing unnecessary dependencies on IPC code.
*/
class ReaderServices {
public:
virtual ~ReaderServices() = default;
/**
* @returns true if the writer of the CanvasEventRingBuffer has permanently
* stopped processing, otherwise returns false.
*/
virtual bool WriterClosed() = 0;
};
CanvasEventRingBuffer() {}
/**
* Initializes the shared memory used for the ringbuffer and footers.
* @param aOtherPid process ID to share the handles to
* @param aReadHandle handle to the shared memory for the buffer
*/
bool InitBuffer(base::ProcessId aOtherPid,
ipc::SharedMemoryBasic::Handle* aReadHandle);
/**
* Initialize the write side of a CanvasEventRingBuffer returning handles to
* the shared memory for the buffer and the two semaphores for waiting in the
* reader and the writer.
*
* @param aOtherPid process ID to share the handles to
* @param aReadHandle handle to the shared memory for the buffer
* @param aReaderSem reading blocked semaphore
* @param aWriterSem writing blocked semaphore
* @param aWriterServices provides functions required by the writer
* @returns true if initialization succeeds
*/
bool InitWriter(base::ProcessId aOtherPid,
ipc::SharedMemoryBasic::Handle* aReadHandle,
CrossProcessSemaphoreHandle* aReaderSem,
CrossProcessSemaphoreHandle* aWriterSem,
UniquePtr<WriterServices> aWriterServices);
/**
* Initialize the read side of a CanvasEventRingBuffer.
*
* @param aReadHandle handle to the shared memory for the buffer
* @param aReaderSem reading blocked semaphore
* @param aWriterSem writing blocked semaphore
* @param aReaderServices provides functions required by the reader
* @returns true if initialization succeeds
*/
bool InitReader(ipc::SharedMemoryBasic::Handle aReadHandle,
CrossProcessSemaphoreHandle aReaderSem,
CrossProcessSemaphoreHandle aWriterSem,
UniquePtr<ReaderServices> aReaderServices);
/**
* Set a new buffer to resume after we have been stopped by the writer.
*
* @param aReadHandle handle to the shared memory for the buffer
* @returns true if initialization succeeds
*/
bool SetNewBuffer(ipc::SharedMemoryBasic::Handle aReadHandle);
bool IsValid() const { return mSharedMemory; }
bool good() const final { return mGood; }
bool WriterFailed() const { return mWrite && mWrite->state == State::Failed; }
void SetIsBad() final {
mGood = false;
mRead->state = State::Failed;
}
void write(const char* const aData, const size_t aSize) final;
bool HasDataToRead();
/*
* This will put the reader into a stopped state if there is no more data to
* read. If this returns false the caller is responsible for continuing
* translation at a later point. If it returns false the writer will start the
* translation again when more data is written.
*
* @returns true if stopped
*/
bool StopIfEmpty();
/*
* Waits for data to become available. This will wait for aTimeout duration
* aRetryCount number of times, checking to see if the other side is closed in
* between each one.
*
* @param aTimeout duration to wait
* @param aRetryCount number of times to retry
* @returns true if data is available to read.
*/
bool WaitForDataToRead(TimeDuration aTimeout, int32_t aRetryCount);
uint8_t ReadNextEvent();
void read(char* const aOut, const size_t aSize) final;
/**
* Writes a checkpoint event to the buffer.
*
* @returns the write count after the checkpoint has been written
*/
uint32_t CreateCheckpoint();
/**
* Waits until the given checkpoint has been read from the buffer.
*
* @params aCheckpoint the checkpoint to wait for
* @params aTimeout duration to wait while reader is not active
* @returns true if the checkpoint was reached, false if the reader is closed
* or we timeout.
*/
bool WaitForCheckpoint(uint32_t aCheckpoint);
/**
* Switch to a different sized buffer.
*/
bool SwitchBuffer(base::ProcessId aOtherPid,
ipc::SharedMemoryBasic::Handle* aHandle);
/**
* Used to send data back to the writer. This is done through the same shared
* memory so the writer must wait and read the response after it has submitted
* the event that uses this.
*
* @param aData the data to be written back to the writer
* @param aSize the number of chars to write
*/
void ReturnWrite(const char* aData, size_t aSize);
/**
* Used to read data sent back from the reader via ReturnWrite. This is done
* through the same shared memory so the writer must wait until all expected
* data is read before writing new events to the buffer.
*
* @param aOut the pointer to read into
* @param aSize the number of chars to read
*/
void ReturnRead(char* aOut, size_t aSize);
bool UsingLargeStream() { return mLargeStream; }
protected:
bool WaitForAndRecalculateAvailableSpace() final;
void UpdateWriteTotalsBy(uint32_t aCount) final;
private:
enum class State : uint32_t { enum class State : uint32_t {
Processing, Processing,
@ -49,65 +212,89 @@ class CanvasDrawEventRecorder final : public gfx::DrawEventRecorderPrivate,
*/ */
AboutToWait, AboutToWait,
Waiting, Waiting,
Paused,
Stopped, Stopped,
Failed, Failed,
}; };
struct Header { struct ReadFooter {
union { Atomic<uint32_t> count;
struct { Atomic<uint32_t> returnCount;
Atomic<int64_t> eventCount; Atomic<State> state;
Atomic<int64_t> writerWaitCount;
Atomic<State> writerState;
};
uint8_t padding1[64];
};
Atomic<int64_t> processedCount;
Atomic<State> readerState;
}; };
class Helpers { struct WriteFooter {
public: Atomic<uint32_t> count;
virtual ~Helpers() = default; Atomic<uint32_t> returnCount;
Atomic<uint32_t> requiredDifference;
virtual bool InitTranslator(const TextureType& aTextureType, Atomic<State> state;
Handle&& aReadHandle,
nsTArray<Handle>&& aBufferHandles,
const uint64_t& aBufferSize,
CrossProcessSemaphoreHandle&& aReaderSem,
CrossProcessSemaphoreHandle&& aWriterSem,
const bool& aUseIPDLThread) = 0;
virtual bool AddBuffer(Handle&& aBufferHandle,
const uint64_t& aBufferSize) = 0;
/**
* @returns true if the reader of the CanvasEventRingBuffer has permanently
* stopped processing, otherwise returns false.
*/
virtual bool ReaderClosed() = 0;
/**
* Causes the reader to resume processing when it is in a stopped state.
*/
virtual bool RestartReader() = 0;
}; };
bool Init(TextureType aTextureType, UniquePtr<Helpers> aHelpers); CanvasEventRingBuffer(const CanvasEventRingBuffer&) = delete;
void operator=(const CanvasEventRingBuffer&) = delete;
/** void IncrementWriteCountBy(uint32_t aCount);
* Record an event for processing by the CanvasParent's CanvasTranslator.
* @param aEvent the event to record bool WaitForReadCount(uint32_t aReadCount, TimeDuration aTimeout);
*/
void RecordEvent(const gfx::RecordedEvent& aEvent) final; bool WaitForAndRecalculateAvailableData();
void UpdateReadTotalsBy(uint32_t aCount);
void IncrementReadCountBy(uint32_t aCount);
void CheckAndSignalReader();
void CheckAndSignalWriter();
uint32_t WaitForBytesToWrite();
uint32_t WaitForBytesToRead();
uint32_t StreamSize();
RefPtr<ipc::SharedMemoryBasic> mSharedMemory;
UniquePtr<CrossProcessSemaphore> mReaderSemaphore;
UniquePtr<CrossProcessSemaphore> mWriterSemaphore;
UniquePtr<WriterServices> mWriterServices;
UniquePtr<ReaderServices> mReaderServices;
char* mBuf = nullptr;
uint32_t mOurCount = 0;
WriteFooter* mWrite = nullptr;
ReadFooter* mRead = nullptr;
bool mGood = false;
bool mLargeStream = true;
};
class CanvasDrawEventRecorder final : public gfx::DrawEventRecorderPrivate {
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(CanvasDrawEventRecorder, final)
explicit CanvasDrawEventRecorder(){};
bool Init(base::ProcessId aOtherPid, ipc::SharedMemoryBasic::Handle* aHandle,
CrossProcessSemaphoreHandle* aReaderSem,
CrossProcessSemaphoreHandle* aWriterSem,
UniquePtr<CanvasEventRingBuffer::WriterServices> aWriterServices) {
return mOutputStream.InitWriter(aOtherPid, aHandle, aReaderSem, aWriterSem,
std::move(aWriterServices));
}
void RecordEvent(const gfx::RecordedEvent& aEvent) final {
if (!mOutputStream.good()) {
return;
}
aEvent.RecordToStream(mOutputStream);
}
void StoreSourceSurfaceRecording(gfx::SourceSurface* aSurface, void StoreSourceSurfaceRecording(gfx::SourceSurface* aSurface,
const char* aReason) final; const char* aReason) final;
void Flush() final {} void Flush() final {}
int64_t CreateCheckpoint(); void ReturnRead(char* aOut, size_t aSize) {
mOutputStream.ReturnRead(aOut, aSize);
}
uint32_t CreateCheckpoint() { return mOutputStream.CreateCheckpoint(); }
/** /**
* Waits until the given checkpoint has been read by the translator. * Waits until the given checkpoint has been read by the translator.
@ -116,60 +303,19 @@ class CanvasDrawEventRecorder final : public gfx::DrawEventRecorderPrivate,
* @returns true if the checkpoint was reached, false if the reader is closed * @returns true if the checkpoint was reached, false if the reader is closed
* or we timeout. * or we timeout.
*/ */
bool WaitForCheckpoint(int64_t aCheckpoint); bool WaitForCheckpoint(uint32_t aCheckpoint) {
return mOutputStream.WaitForCheckpoint(aCheckpoint);
}
TextureType GetTextureType() { return mTextureType; } bool UsingLargeStream() { return mOutputStream.UsingLargeStream(); }
void DropFreeBuffers(); bool SwitchBuffer(base::ProcessId aOtherPid,
ipc::SharedMemoryBasic::Handle* aHandle) {
protected: return mOutputStream.SwitchBuffer(aOtherPid, aHandle);
gfx::ContiguousBuffer& GetContiguousBuffer(size_t aSize) final; }
void IncrementEventCount() final;
private: private:
void WriteInternalEvent(EventType aEventType); CanvasEventRingBuffer mOutputStream;
void CheckAndSignalReader();
size_t mDefaultBufferSize;
uint32_t mMaxSpinCount;
uint32_t mDropBufferLimit;
uint32_t mDropBufferOnZero;
UniquePtr<Helpers> mHelpers;
TextureType mTextureType = TextureType::Unknown;
RefPtr<ipc::SharedMemoryBasic> mHeaderShmem;
Header* mHeader = nullptr;
struct CanvasBuffer : public gfx::ContiguousBuffer {
RefPtr<ipc::SharedMemoryBasic> shmem;
CanvasBuffer() : ContiguousBuffer(nullptr) {}
explicit CanvasBuffer(RefPtr<ipc::SharedMemoryBasic>&& aShmem)
: ContiguousBuffer(static_cast<char*>(aShmem->memory()),
aShmem->Size()),
shmem(std::move(aShmem)) {}
size_t Capacity() { return shmem->Size(); }
};
struct RecycledBuffer {
RefPtr<ipc::SharedMemoryBasic> shmem;
int64_t eventCount = 0;
explicit RecycledBuffer(RefPtr<ipc::SharedMemoryBasic>&& aShmem,
int64_t aEventCount)
: shmem(std::move(aShmem)), eventCount(aEventCount) {}
size_t Capacity() { return shmem->Size(); }
};
CanvasBuffer mCurrentBuffer;
std::queue<RecycledBuffer> mRecycledBuffers;
UniquePtr<CrossProcessSemaphore> mWriterSemaphore;
UniquePtr<CrossProcessSemaphore> mReaderSemaphore;
}; };
} // namespace layers } // namespace layers

View file

@ -40,11 +40,6 @@ const EventType REMOVE_SURFACE_ALIAS = EventType(EventType::LAST + 9);
const EventType DEVICE_CHANGE_ACKNOWLEDGED = EventType(EventType::LAST + 10); const EventType DEVICE_CHANGE_ACKNOWLEDGED = EventType(EventType::LAST + 10);
const EventType NEXT_TEXTURE_ID = EventType(EventType::LAST + 11); const EventType NEXT_TEXTURE_ID = EventType(EventType::LAST + 11);
const EventType TEXTURE_DESTRUCTION = EventType(EventType::LAST + 12); const EventType TEXTURE_DESTRUCTION = EventType(EventType::LAST + 12);
const EventType CHECKPOINT = EventType(EventType::LAST + 13);
const EventType PAUSE_TRANSLATION = EventType(EventType::LAST + 14);
const EventType RECYCLE_BUFFER = EventType(EventType::LAST + 15);
const EventType DROP_BUFFER = EventType(EventType::LAST + 16);
const EventType LAST_CANVAS_EVENT_TYPE = DROP_BUFFER;
class RecordedCanvasBeginTransaction final class RecordedCanvasBeginTransaction final
: public RecordedEventDerived<RecordedCanvasBeginTransaction> { : public RecordedEventDerived<RecordedCanvasBeginTransaction> {
@ -349,7 +344,31 @@ class RecordedGetDataForSurface final
inline bool RecordedGetDataForSurface::PlayCanvasEvent( inline bool RecordedGetDataForSurface::PlayCanvasEvent(
CanvasTranslator* aTranslator) const { CanvasTranslator* aTranslator) const {
aTranslator->GetDataSurface(mSurface.mLongPtr); gfx::SourceSurface* surface = aTranslator->LookupSourceSurface(mSurface);
if (!surface) {
return false;
}
UniquePtr<gfx::DataSourceSurface::ScopedMap> map =
aTranslator->GetPreparedMap(mSurface);
if (!map) {
return false;
}
int32_t dataFormatWidth =
surface->GetSize().width * BytesPerPixel(surface->GetFormat());
int32_t srcStride = map->GetStride();
if (dataFormatWidth > srcStride) {
return false;
}
char* src = reinterpret_cast<char*>(map->GetData());
char* endSrc = src + (map->GetSurface()->GetSize().height * srcStride);
while (src < endSrc) {
aTranslator->ReturnWrite(src, dataFormatWidth);
src += srcStride;
}
return true; return true;
} }
@ -557,87 +576,6 @@ RecordedTextureDestruction::RecordedTextureDestruction(S& aStream)
ReadElement(aStream, mTextureId); ReadElement(aStream, mTextureId);
} }
class RecordedCheckpoint final
: public RecordedEventDerived<RecordedCheckpoint> {
public:
RecordedCheckpoint() : RecordedEventDerived(CHECKPOINT) {}
template <class S>
MOZ_IMPLICIT RecordedCheckpoint(S& aStream)
: RecordedEventDerived(CHECKPOINT) {}
bool PlayCanvasEvent(CanvasTranslator* aTranslator) const {
aTranslator->CheckpointReached();
return true;
}
template <class S>
void Record(S& aStream) const {}
std::string GetName() const final { return "RecordedCheckpoint"; }
};
class RecordedPauseTranslation final
: public RecordedEventDerived<RecordedPauseTranslation> {
public:
RecordedPauseTranslation() : RecordedEventDerived(PAUSE_TRANSLATION) {}
template <class S>
MOZ_IMPLICIT RecordedPauseTranslation(S& aStream)
: RecordedEventDerived(PAUSE_TRANSLATION) {}
bool PlayCanvasEvent(CanvasTranslator* aTranslator) const {
aTranslator->PauseTranslation();
return true;
}
template <class S>
void Record(S& aStream) const {}
std::string GetName() const final { return "RecordedPauseTranslation"; }
};
class RecordedRecycleBuffer final
: public RecordedEventDerived<RecordedRecycleBuffer> {
public:
RecordedRecycleBuffer() : RecordedEventDerived(RECYCLE_BUFFER) {}
template <class S>
MOZ_IMPLICIT RecordedRecycleBuffer(S& aStream)
: RecordedEventDerived(RECYCLE_BUFFER) {}
bool PlayCanvasEvent(CanvasTranslator* aTranslator) const {
aTranslator->RecycleBuffer();
return true;
}
template <class S>
void Record(S& aStream) const {}
std::string GetName() const final { return "RecordedNextBuffer"; }
};
class RecordedDropBuffer final
: public RecordedEventDerived<RecordedDropBuffer> {
public:
RecordedDropBuffer() : RecordedEventDerived(DROP_BUFFER) {}
template <class S>
MOZ_IMPLICIT RecordedDropBuffer(S& aStream)
: RecordedEventDerived(DROP_BUFFER) {}
bool PlayCanvasEvent(CanvasTranslator* aTranslator) const {
// Use the next buffer without recycling which drops the current buffer.
aTranslator->NextBuffer();
return true;
}
template <class S>
void Record(S& aStream) const {}
std::string GetName() const final { return "RecordedDropAndMoveNextBuffer"; }
};
#define FOR_EACH_CANVAS_EVENT(f) \ #define FOR_EACH_CANVAS_EVENT(f) \
f(CANVAS_BEGIN_TRANSACTION, RecordedCanvasBeginTransaction); \ f(CANVAS_BEGIN_TRANSACTION, RecordedCanvasBeginTransaction); \
f(CANVAS_END_TRANSACTION, RecordedCanvasEndTransaction); \ f(CANVAS_END_TRANSACTION, RecordedCanvasEndTransaction); \
@ -651,11 +589,7 @@ class RecordedDropBuffer final
f(REMOVE_SURFACE_ALIAS, RecordedRemoveSurfaceAlias); \ f(REMOVE_SURFACE_ALIAS, RecordedRemoveSurfaceAlias); \
f(DEVICE_CHANGE_ACKNOWLEDGED, RecordedDeviceChangeAcknowledged); \ f(DEVICE_CHANGE_ACKNOWLEDGED, RecordedDeviceChangeAcknowledged); \
f(NEXT_TEXTURE_ID, RecordedNextTextureId); \ f(NEXT_TEXTURE_ID, RecordedNextTextureId); \
f(TEXTURE_DESTRUCTION, RecordedTextureDestruction); \ f(TEXTURE_DESTRUCTION, RecordedTextureDestruction);
f(CHECKPOINT, RecordedCheckpoint); \
f(PAUSE_TRANSLATION, RecordedPauseTranslation); \
f(RECYCLE_BUFFER, RecordedRecycleBuffer); \
f(DROP_BUFFER, RecordedDropBuffer);
} // namespace layers } // namespace layers
} // namespace mozilla } // namespace mozilla

View file

@ -23,7 +23,7 @@ RecordedTextureData::RecordedTextureData(
already_AddRefed<CanvasChild> aCanvasChild, gfx::IntSize aSize, already_AddRefed<CanvasChild> aCanvasChild, gfx::IntSize aSize,
gfx::SurfaceFormat aFormat, TextureType aTextureType) gfx::SurfaceFormat aFormat, TextureType aTextureType)
: mCanvasChild(aCanvasChild), mSize(aSize), mFormat(aFormat) { : mCanvasChild(aCanvasChild), mSize(aSize), mFormat(aFormat) {
mCanvasChild->EnsureRecorder(aSize, aFormat, aTextureType); mCanvasChild->EnsureRecorder(aTextureType);
} }
RecordedTextureData::~RecordedTextureData() { RecordedTextureData::~RecordedTextureData() {

View file

@ -20,7 +20,6 @@
#include "mozilla/ipc/FileDescriptor.h" #include "mozilla/ipc/FileDescriptor.h"
#include "mozilla/layers/CompositorTypes.h" // for TextureFlags, etc #include "mozilla/layers/CompositorTypes.h" // for TextureFlags, etc
#include "mozilla/layers/LayersTypes.h" // for LayerRenderState, etc #include "mozilla/layers/LayersTypes.h" // for LayerRenderState, etc
#include "mozilla/layers/LayersMessages.h"
#include "mozilla/layers/LayersSurfaces.h" #include "mozilla/layers/LayersSurfaces.h"
#include "mozilla/layers/TextureSourceProvider.h" #include "mozilla/layers/TextureSourceProvider.h"
#include "mozilla/mozalloc.h" // for operator delete #include "mozilla/mozalloc.h" // for operator delete

View file

@ -15,42 +15,21 @@
#include "mozilla/ipc/Endpoint.h" #include "mozilla/ipc/Endpoint.h"
#include "mozilla/ipc/ProcessChild.h" #include "mozilla/ipc/ProcessChild.h"
#include "mozilla/layers/CanvasDrawEventRecorder.h" #include "mozilla/layers/CanvasDrawEventRecorder.h"
#include "mozilla/layers/SourceSurfaceSharedData.h"
#include "mozilla/Maybe.h"
#include "nsIObserverService.h" #include "nsIObserverService.h"
#include "RecordedCanvasEventImpl.h" #include "RecordedCanvasEventImpl.h"
namespace mozilla { namespace mozilla {
namespace layers { namespace layers {
class RecorderHelpers final : public CanvasDrawEventRecorder::Helpers { /* static */ bool CanvasChild::mInForeground = true;
class RingBufferWriterServices final
: public CanvasEventRingBuffer::WriterServices {
public: public:
explicit RecorderHelpers(const RefPtr<CanvasChild>& aCanvasChild) explicit RingBufferWriterServices(RefPtr<CanvasChild> aCanvasChild)
: mCanvasChild(aCanvasChild) {} : mCanvasChild(aCanvasChild) {}
~RecorderHelpers() override = default; ~RingBufferWriterServices() override = default;
bool InitTranslator(const TextureType& aTextureType, Handle&& aReadHandle,
nsTArray<Handle>&& aBufferHandles,
const uint64_t& aBufferSize,
CrossProcessSemaphoreHandle&& aReaderSem,
CrossProcessSemaphoreHandle&& aWriterSem,
const bool& aUseIPDLThread) override {
if (!mCanvasChild) {
return false;
}
return mCanvasChild->SendInitTranslator(
aTextureType, std::move(aReadHandle), std::move(aBufferHandles),
aBufferSize, std::move(aReaderSem), std::move(aWriterSem),
aUseIPDLThread);
}
bool AddBuffer(Handle&& aBufferHandle, const uint64_t& aBufferSize) override {
if (!mCanvasChild) {
return false;
}
return mCanvasChild->SendAddBuffer(std::move(aBufferHandle), aBufferSize);
}
bool ReaderClosed() override { bool ReaderClosed() override {
if (!mCanvasChild) { if (!mCanvasChild) {
@ -59,11 +38,11 @@ class RecorderHelpers final : public CanvasDrawEventRecorder::Helpers {
return !mCanvasChild->CanSend() || ipc::ProcessChild::ExpectingShutdown(); return !mCanvasChild->CanSend() || ipc::ProcessChild::ExpectingShutdown();
} }
bool RestartReader() override { void ResumeReader() override {
if (!mCanvasChild) { if (!mCanvasChild) {
return false; return;
} }
return mCanvasChild->SendRestartTranslation(); mCanvasChild->ResumeTranslation();
} }
private: private:
@ -176,24 +155,45 @@ ipc::IPCResult CanvasChild::RecvDeactivate() {
return IPC_OK(); return IPC_OK();
} }
void CanvasChild::EnsureRecorder(gfx::IntSize aSize, gfx::SurfaceFormat aFormat, void CanvasChild::EnsureRecorder(TextureType aTextureType) {
TextureType aTextureType) {
if (!mRecorder) { if (!mRecorder) {
auto recorder = MakeRefPtr<CanvasDrawEventRecorder>(); MOZ_ASSERT(mTextureType == TextureType::Unknown);
if (!recorder->Init(aTextureType, MakeUnique<RecorderHelpers>(this))) { 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; return;
} }
mRecorder = recorder.forget(); if (CanSend()) {
Unused << SendInitTranslator(mTextureType, std::move(handle),
std::move(readerSem), std::move(writerSem),
/* aUseIPDLThread */ false);
}
} }
MOZ_RELEASE_ASSERT(mRecorder->GetTextureType() == aTextureType, MOZ_RELEASE_ASSERT(mTextureType == aTextureType,
"We only support one remote TextureType currently."); "We only support one remote TextureType currently.");
EnsureDataSurfaceShmem(aSize, aFormat);
} }
void CanvasChild::ActorDestroy(ActorDestroyReason aWhy) {} 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() { void CanvasChild::Destroy() {
if (CanSend()) { if (CanSend()) {
@ -202,11 +202,24 @@ void CanvasChild::Destroy() {
} }
void CanvasChild::OnTextureWriteLock() { void CanvasChild::OnTextureWriteLock() {
// We drop mRecorder in ActorDestroy to break the reference cycle.
if (!mRecorder) {
return;
}
mHasOutstandingWriteLock = true; mHasOutstandingWriteLock = true;
mLastWriteLockCheckpoint = mRecorder->CreateCheckpoint(); mLastWriteLockCheckpoint = mRecorder->CreateCheckpoint();
} }
void CanvasChild::OnTextureForwarded() { 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) { if (mHasOutstandingWriteLock) {
mRecorder->RecordEvent(RecordedCanvasFlush()); mRecorder->RecordEvent(RecordedCanvasFlush());
if (!mRecorder->WaitForCheckpoint(mLastWriteLockCheckpoint)) { if (!mRecorder->WaitForCheckpoint(mLastWriteLockCheckpoint)) {
@ -225,8 +238,24 @@ void CanvasChild::OnTextureForwarded() {
} }
bool CanvasChild::EnsureBeginTransaction() { bool CanvasChild::EnsureBeginTransaction() {
// We drop mRecorder in ActorDestroy to break the reference cycle.
if (!mRecorder) {
return false;
}
if (!mIsInTransaction) { if (!mIsInTransaction) {
RecordEvent(RecordedCanvasBeginTransaction()); // 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; mIsInTransaction = true;
} }
@ -234,33 +263,25 @@ bool CanvasChild::EnsureBeginTransaction() {
} }
void CanvasChild::EndTransaction() { void CanvasChild::EndTransaction() {
// We drop mRecorder in ActorDestroy to break the reference cycle.
if (!mRecorder) {
return;
}
if (mIsInTransaction) { if (mIsInTransaction) {
RecordEvent(RecordedCanvasEndTransaction()); mRecorder->RecordEvent(RecordedCanvasEndTransaction());
mIsInTransaction = false; mIsInTransaction = false;
mDormant = false;
} else {
// 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; ++mTransactionsSinceGetDataSurface;
} }
void CanvasChild::DropFreeBuffersWhenDormant() { /* static */
// Drop any free buffers if we have not had any non-empty transactions. void CanvasChild::ClearCachedResources() {
if (mDormant) { // We use this as a proxy for the tab being in the backgound.
mRecorder->DropFreeBuffers(); mInForeground = false;
}
} }
void CanvasChild::ClearCachedResources() { mRecorder->DropFreeBuffers(); }
bool CanvasChild::ShouldBeCleanedUp() const { bool CanvasChild::ShouldBeCleanedUp() const {
// Always return true if we've been deactivated. // Always return true if we've been deactivated.
if (Deactivated()) { if (Deactivated()) {
@ -268,11 +289,16 @@ bool CanvasChild::ShouldBeCleanedUp() const {
} }
// We can only be cleaned up if nothing else references our recorder. // We can only be cleaned up if nothing else references our recorder.
return mRecorder->hasOneRef(); return !mRecorder || mRecorder->hasOneRef();
} }
already_AddRefed<gfx::DrawTarget> CanvasChild::CreateDrawTarget( already_AddRefed<gfx::DrawTarget> CanvasChild::CreateDrawTarget(
gfx::IntSize aSize, gfx::SurfaceFormat aFormat) { 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( RefPtr<gfx::DrawTarget> dummyDt = gfx::Factory::CreateDrawTarget(
gfx::BackendType::SKIA, gfx::IntSize(1, 1), aFormat); gfx::BackendType::SKIA, gfx::IntSize(1, 1), aFormat);
RefPtr<gfx::DrawTarget> dt = MakeAndAddRef<gfx::DrawTargetRecording>( RefPtr<gfx::DrawTarget> dt = MakeAndAddRef<gfx::DrawTargetRecording>(
@ -280,34 +306,6 @@ already_AddRefed<gfx::DrawTarget> CanvasChild::CreateDrawTarget(
return dt.forget(); return dt.forget();
} }
bool CanvasChild::EnsureDataSurfaceShmem(gfx::IntSize aSize,
gfx::SurfaceFormat aFormat) {
size_t dataFormatWidth = aSize.width * BytesPerPixel(aFormat);
size_t sizeRequired =
ipc::SharedMemory::PageAlignedSize(dataFormatWidth * aSize.height);
if (!mDataSurfaceShmem || 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();
}
return true;
}
void CanvasChild::RecordEvent(const gfx::RecordedEvent& aEvent) { void CanvasChild::RecordEvent(const gfx::RecordedEvent& aEvent) {
// We drop mRecorder in ActorDestroy to break the reference cycle. // We drop mRecorder in ActorDestroy to break the reference cycle.
if (!mRecorder) { if (!mRecorder) {
@ -317,15 +315,16 @@ void CanvasChild::RecordEvent(const gfx::RecordedEvent& aEvent) {
mRecorder->RecordEvent(aEvent); mRecorder->RecordEvent(aEvent);
} }
int64_t CanvasChild::CreateCheckpoint() {
return mRecorder->CreateCheckpoint();
}
already_AddRefed<gfx::DataSourceSurface> CanvasChild::GetDataSurface( already_AddRefed<gfx::DataSourceSurface> CanvasChild::GetDataSurface(
const gfx::SourceSurface* aSurface) { const gfx::SourceSurface* aSurface) {
MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread()); MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aSurface); 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 // mTransactionsSinceGetDataSurface is used to determine if we want to prepare
// a DataSourceSurface in the GPU process up front at the end of the // 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 // transaction, but that only makes sense if the canvas JS is requesting data
@ -338,56 +337,43 @@ already_AddRefed<gfx::DataSourceSurface> CanvasChild::GetDataSurface(
return nullptr; return nullptr;
} }
RecordEvent(RecordedPrepareDataForSurface(aSurface)); mRecorder->RecordEvent(RecordedPrepareDataForSurface(aSurface));
uint32_t checkpoint = mRecorder->CreateCheckpoint();
gfx::IntSize ssSize = aSurface->GetSize(); gfx::IntSize ssSize = aSurface->GetSize();
gfx::SurfaceFormat ssFormat = aSurface->GetFormat(); gfx::SurfaceFormat ssFormat = aSurface->GetFormat();
if (!EnsureDataSurfaceShmem(ssSize, ssFormat)) { 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; 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();
}
RecordEvent(RecordedGetDataForSurface(aSurface)); mRecorder->RecordEvent(RecordedGetDataForSurface(aSurface));
auto checkpoint = CreateCheckpoint(); mRecorder->ReturnRead(dest, ssSize.height * dataFormatWidth);
struct DataShmemHolder {
RefPtr<ipc::SharedMemoryBasic> shmem;
RefPtr<CanvasChild> canvasChild;
};
auto* data = static_cast<uint8_t*>(mDataSurfaceShmem->memory());
auto* closure = new DataShmemHolder{mDataSurfaceShmem.forget(), this};
auto dataFormatWidth = ssSize.width * BytesPerPixel(ssFormat);
RefPtr<gfx::DataSourceSurface> dataSurface =
gfx::Factory::CreateWrappingDataSourceSurface(
data, dataFormatWidth, ssSize, ssFormat,
[](void* aClosure) {
auto* shmemHolder = static_cast<DataShmemHolder*>(aClosure);
shmemHolder->canvasChild->ReturnDataSurfaceShmem(
shmemHolder->shmem.forget());
delete shmemHolder;
},
closure);
mRecorder->WaitForCheckpoint(checkpoint);
return dataSurface.forget(); return dataSurface.forget();
} }
already_AddRefed<gfx::SourceSurface> CanvasChild::WrapSurface( already_AddRefed<gfx::SourceSurface> CanvasChild::WrapSurface(
const RefPtr<gfx::SourceSurface>& aSurface) { const RefPtr<gfx::SourceSurface>& aSurface) {
if (!aSurface) { MOZ_ASSERT(aSurface);
// We drop mRecorder in ActorDestroy to break the reference cycle.
if (!mRecorder) {
return nullptr; return nullptr;
} }
return MakeAndAddRef<SourceSurfaceCanvasRecording>(aSurface, this, mRecorder); return MakeAndAddRef<SourceSurfaceCanvasRecording>(aSurface, this, mRecorder);
} }
void CanvasChild::ReturnDataSurfaceShmem(
already_AddRefed<ipc::SharedMemoryBasic> aDataSurfaceShmem) {
RefPtr<ipc::SharedMemoryBasic> data = aDataSurfaceShmem;
if (!mDataSurfaceShmem || data->Size() > mDataSurfaceShmem->Size()) {
mDataSurfaceShmem = data.forget();
}
}
} // namespace layers } // namespace layers
} // namespace mozilla } // namespace mozilla

View file

@ -12,6 +12,8 @@
#include "mozilla/layers/PCanvasChild.h" #include "mozilla/layers/PCanvasChild.h"
#include "mozilla/layers/SourceSurfaceSharedData.h" #include "mozilla/layers/SourceSurfaceSharedData.h"
#include "mozilla/WeakPtr.h" #include "mozilla/WeakPtr.h"
#include "nsRefPtrHashtable.h"
#include "nsTArray.h"
namespace mozilla { namespace mozilla {
@ -36,7 +38,7 @@ class CanvasChild final : public PCanvasChild, public SupportsWeakPtr {
/** /**
* Release resources until they are next required. * Release resources until they are next required.
*/ */
void ClearCachedResources(); static void ClearCachedResources();
ipc::IPCResult RecvNotifyDeviceChanged(); ipc::IPCResult RecvNotifyDeviceChanged();
@ -47,8 +49,12 @@ class CanvasChild final : public PCanvasChild, public SupportsWeakPtr {
* *
* @params aTextureType the TextureType to create in the CanvasTranslator. * @params aTextureType the TextureType to create in the CanvasTranslator.
*/ */
void EnsureRecorder(gfx::IntSize aSize, gfx::SurfaceFormat aFormat, void EnsureRecorder(TextureType aTextureType);
TextureType aTextureType);
/**
* Send a messsage to our CanvasParent to resume translation.
*/
void ResumeTranslation();
/** /**
* Clean up IPDL actor. * Clean up IPDL actor.
@ -105,8 +111,6 @@ class CanvasChild final : public PCanvasChild, public SupportsWeakPtr {
*/ */
void RecordEvent(const gfx::RecordedEvent& aEvent); void RecordEvent(const gfx::RecordedEvent& aEvent);
int64_t CreateCheckpoint();
/** /**
* Wrap the given surface, so that we can provide a DataSourceSurface if * Wrap the given surface, so that we can provide a DataSourceSurface if
* required. * required.
@ -135,26 +139,18 @@ class CanvasChild final : public PCanvasChild, public SupportsWeakPtr {
~CanvasChild() final; ~CanvasChild() final;
bool EnsureDataSurfaceShmem(gfx::IntSize aSize, gfx::SurfaceFormat aFormat);
void ReturnDataSurfaceShmem(
already_AddRefed<ipc::SharedMemoryBasic> aDataSurfaceShmem);
void DropFreeBuffersWhenDormant();
static const uint32_t kCacheDataSurfaceThreshold = 10; static const uint32_t kCacheDataSurfaceThreshold = 10;
static bool mDeactivated; static bool mDeactivated;
static bool mInForeground;
RefPtr<CanvasDrawEventRecorder> mRecorder; RefPtr<CanvasDrawEventRecorder> mRecorder;
TextureType mTextureType = TextureType::Unknown;
RefPtr<ipc::SharedMemoryBasic> mDataSurfaceShmem; uint32_t mLastWriteLockCheckpoint = 0;
int64_t mLastWriteLockCheckpoint = 0;
uint32_t mTransactionsSinceGetDataSurface = kCacheDataSurfaceThreshold; uint32_t mTransactionsSinceGetDataSurface = kCacheDataSurfaceThreshold;
std::vector<RefPtr<gfx::SourceSurface>> mLastTransactionExternalSurfaces; std::vector<RefPtr<gfx::SourceSurface>> mLastTransactionExternalSurfaces;
bool mIsInTransaction = false; bool mIsInTransaction = false;
bool mHasOutstandingWriteLock = false; bool mHasOutstandingWriteLock = false;
bool mDormant = false;
}; };
} // namespace layers } // namespace layers

View file

@ -14,10 +14,8 @@
#include "mozilla/gfx/GPUParent.h" #include "mozilla/gfx/GPUParent.h"
#include "mozilla/gfx/Logging.h" #include "mozilla/gfx/Logging.h"
#include "mozilla/ipc/Endpoint.h" #include "mozilla/ipc/Endpoint.h"
#include "mozilla/layers/CanvasTranslator.h"
#include "mozilla/layers/SharedSurfacesParent.h" #include "mozilla/layers/SharedSurfacesParent.h"
#include "mozilla/layers/TextureClient.h" #include "mozilla/layers/TextureClient.h"
#include "mozilla/StaticPrefs_gfx.h"
#include "mozilla/SyncRunnable.h" #include "mozilla/SyncRunnable.h"
#include "mozilla/TaskQueue.h" #include "mozilla/TaskQueue.h"
#include "mozilla/Telemetry.h" #include "mozilla/Telemetry.h"
@ -31,6 +29,25 @@
namespace mozilla { namespace mozilla {
namespace layers { namespace layers {
// 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.
static const TimeDuration kReadEventTimeout = TimeDuration::FromMilliseconds(5);
class RingBufferReaderServices final
: public CanvasEventRingBuffer::ReaderServices {
public:
explicit RingBufferReaderServices(RefPtr<CanvasTranslator> aCanvasTranslator)
: mCanvasTranslator(std::move(aCanvasTranslator)) {}
~RingBufferReaderServices() final = default;
bool WriterClosed() final { return !mCanvasTranslator->CanSend(); }
private:
RefPtr<CanvasTranslator> mCanvasTranslator;
};
TextureData* CanvasTranslator::CreateTextureData(TextureType aTextureType, TextureData* CanvasTranslator::CreateTextureData(TextureType aTextureType,
const gfx::IntSize& aSize, const gfx::IntSize& aSize,
gfx::SurfaceFormat aFormat) { gfx::SurfaceFormat aFormat) {
@ -51,10 +68,6 @@ TextureData* CanvasTranslator::CreateTextureData(TextureType aTextureType,
} }
CanvasTranslator::CanvasTranslator() { CanvasTranslator::CanvasTranslator() {
mMaxSpinCount = StaticPrefs::gfx_canvas_remote_max_spin_count();
mNextEventTimeout = TimeDuration::FromMilliseconds(
StaticPrefs::gfx_canvas_remote_event_timeout_ms());
// Track when remote canvas has been activated. // Track when remote canvas has been activated.
Telemetry::ScalarAdd(Telemetry::ScalarID::GFX_CANVAS_REMOTE_ACTIVATED, 1); Telemetry::ScalarAdd(Telemetry::ScalarID::GFX_CANVAS_REMOTE_ACTIVATED, 1);
} }
@ -82,49 +95,31 @@ bool CanvasTranslator::IsInTaskQueue() const {
return gfx::CanvasRenderThread::IsInCanvasRenderThread(); return gfx::CanvasRenderThread::IsInCanvasRenderThread();
} }
static bool CreateAndMapShmem(RefPtr<ipc::SharedMemoryBasic>& aShmem,
Handle&& aHandle,
ipc::SharedMemory::OpenRights aOpenRights,
size_t aSize) {
auto shmem = MakeRefPtr<ipc::SharedMemoryBasic>();
if (!shmem->SetHandle(std::move(aHandle), aOpenRights) ||
!shmem->Map(aSize)) {
return false;
}
shmem->CloseHandle();
aShmem = shmem.forget();
return true;
}
mozilla::ipc::IPCResult CanvasTranslator::RecvInitTranslator( mozilla::ipc::IPCResult CanvasTranslator::RecvInitTranslator(
const TextureType& aTextureType, Handle&& aReadHandle, const TextureType& aTextureType,
nsTArray<Handle>&& aBufferHandles, uint64_t aBufferSize, ipc::SharedMemoryBasic::Handle&& aReadHandle,
CrossProcessSemaphoreHandle&& aReaderSem, CrossProcessSemaphoreHandle&& aReaderSem,
CrossProcessSemaphoreHandle&& aWriterSem, bool aUseIPDLThread) { CrossProcessSemaphoreHandle&& aWriterSem, const bool& aUseIPDLThread) {
if (mHeaderShmem) { if (mStream) {
return IPC_FAIL(this, "RecvInitTranslator called twice."); return IPC_FAIL(this, "RecvInitTranslator called twice.");
} }
mTextureType = aTextureType; mTextureType = aTextureType;
mHeaderShmem = MakeAndAddRef<ipc::SharedMemoryBasic>(); // We need to initialize the stream first, because it might be used to
if (!CreateAndMapShmem(mHeaderShmem, std::move(aReadHandle), // communicate other failures back to the writer.
ipc::SharedMemory::RightsReadWrite, sizeof(Header))) { mStream = MakeUnique<CanvasEventRingBuffer>();
return IPC_FAIL(this, "Failed."); if (!mStream->InitReader(std::move(aReadHandle), std::move(aReaderSem),
std::move(aWriterSem),
MakeUnique<RingBufferReaderServices>(this))) {
mStream = nullptr;
return IPC_FAIL(this, "Failed to initialize ring buffer reader.");
} }
mHeader = static_cast<Header*>(mHeaderShmem->memory());
mWriterSemaphore.reset(CrossProcessSemaphore::Create(std::move(aWriterSem)));
mWriterSemaphore->CloseHandle();
mReaderSemaphore.reset(CrossProcessSemaphore::Create(std::move(aReaderSem)));
mReaderSemaphore->CloseHandle();
#if defined(XP_WIN) #if defined(XP_WIN)
if (!CheckForFreshCanvasDevice(__LINE__)) { if (!CheckForFreshCanvasDevice(__LINE__)) {
gfxCriticalNote << "GFX: CanvasTranslator failed to get device"; gfxCriticalNote << "GFX: CanvasTranslator failed to get device";
mStream = nullptr;
return IPC_OK(); return IPC_OK();
} }
#endif #endif
@ -132,157 +127,56 @@ mozilla::ipc::IPCResult CanvasTranslator::RecvInitTranslator(
if (!aUseIPDLThread) { if (!aUseIPDLThread) {
mTranslationTaskQueue = gfx::CanvasRenderThread::CreateWorkerTaskQueue(); mTranslationTaskQueue = gfx::CanvasRenderThread::CreateWorkerTaskQueue();
} }
return RecvResumeTranslation();
// 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)) {
return IPC_FAIL(this, "Failed.");
}
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)) {
return IPC_FAIL(this, "Failed.");
}
mCanvasShmems.emplace(std::move(newShmem));
}
DispatchToTaskQueue(NewRunnableMethod("CanvasTranslator::TranslateRecording",
this,
&CanvasTranslator::TranslateRecording));
return IPC_OK();
} }
ipc::IPCResult CanvasTranslator::RecvRestartTranslation() { ipc::IPCResult CanvasTranslator::RecvNewBuffer(
if (mDeactivated) { ipc::SharedMemoryBasic::Handle&& aReadHandle) {
// The other side might have sent a message before we deactivated. if (!mStream) {
return IPC_OK(); return IPC_FAIL(this, "RecvNewBuffer before RecvInitTranslator.");
} }
// We need to set the new buffer on the translation queue to be sure that the
DispatchToTaskQueue(NewRunnableMethod("CanvasTranslator::TranslateRecording", // drop buffer event has been processed.
this, DispatchToTaskQueue(NS_NewRunnableFunction(
&CanvasTranslator::TranslateRecording)); "CanvasTranslator SetNewBuffer",
[self = RefPtr(this), readHandle = std::move(aReadHandle)]() mutable {
return IPC_OK(); self->mStream->SetNewBuffer(std::move(readHandle));
}));
return RecvResumeTranslation();
} }
ipc::IPCResult CanvasTranslator::RecvAddBuffer( ipc::IPCResult CanvasTranslator::RecvResumeTranslation() {
ipc::SharedMemoryBasic::Handle&& aBufferHandle, uint64_t aBufferSize) { if (!mStream) {
if (mDeactivated) { return IPC_FAIL(this, "RecvResumeTranslation before RecvInitTranslator.");
}
if (CheckDeactivated()) {
// The other side might have sent a resume message before we deactivated. // The other side might have sent a resume message before we deactivated.
return IPC_OK(); return IPC_OK();
} }
DispatchToTaskQueue( DispatchToTaskQueue(NewRunnableMethod("CanvasTranslator::StartTranslation",
NewRunnableMethod<ipc::SharedMemoryBasic::Handle&&, size_t>( this,
"CanvasTranslator::AddBuffer", this, &CanvasTranslator::AddBuffer, &CanvasTranslator::StartTranslation));
std::move(aBufferHandle), aBufferSize));
return IPC_OK(); return IPC_OK();
} }
void CanvasTranslator::AddBuffer(ipc::SharedMemoryBasic::Handle&& aBufferHandle, void CanvasTranslator::StartTranslation() {
size_t aBufferSize) { MOZ_RELEASE_ASSERT(mStream->IsValid(),
MOZ_ASSERT(IsInTaskQueue()); "StartTranslation called before buffer has been set.");
MOZ_RELEASE_ASSERT(mHeader->readerState == State::Paused);
// Default sized buffers will have been queued for recycling. if (!TranslateRecording() && CanSend()) {
if (mCurrentShmem.Size() == mDefaultBufferSize) { DispatchToTaskQueue(NewRunnableMethod("CanvasTranslator::StartTranslation",
mCanvasShmems.emplace(std::move(mCurrentShmem)); this,
&CanvasTranslator::StartTranslation));
} }
CanvasShmem newShmem; // If the stream has been marked as bad and the Writer hasn't failed,
if (!CreateAndMapShmem(newShmem.shmem, std::move(aBufferHandle), // deactivate remote canvas.
ipc::SharedMemory::RightsReadOnly, aBufferSize)) { if (!mStream->good() && !mStream->WriterFailed()) {
return; Telemetry::ScalarAdd(
Telemetry::ScalarID::GFX_CANVAS_REMOTE_DEACTIVATED_BAD_STREAM, 1);
Deactivate();
} }
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<ipc::SharedMemoryBasic::Handle&&, size_t>(
"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());
MOZ_RELEASE_ASSERT(mHeader->readerState == State::Paused);
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<void*>(aSurfaceRef);
gfx::SourceSurface* surface = LookupSourceSurface(surfaceRef);
if (!surface) {
return;
}
UniquePtr<gfx::DataSourceSurface::ScopedMap> map = GetPreparedMap(surfaceRef);
if (!map) {
return;
}
auto dstSize = surface->GetSize();
auto srcSize = map->GetSurface()->GetSize();
int32_t dataFormatWidth = dstSize.width * BytesPerPixel(surface->GetFormat());
int32_t srcStride = map->GetStride();
if (dataFormatWidth > srcStride || srcSize != dstSize) {
return;
}
auto requiredSize = dataFormatWidth * dstSize.height;
if (requiredSize <= 0 || size_t(requiredSize) > mDataSurfaceShmem->Size()) {
return;
}
char* dst = static_cast<char*>(mDataSurfaceShmem->memory());
const char* src = reinterpret_cast<char*>(map->GetData());
const char* endSrc = src + (srcSize.height * srcStride);
while (src < endSrc) {
memcpy(dst, src, dataFormatWidth);
src += srcStride;
dst += dataFormatWidth;
}
}
void CanvasTranslator::RecycleBuffer() {
mCanvasShmems.emplace(std::move(mCurrentShmem));
NextBuffer();
}
void CanvasTranslator::NextBuffer() {
mCurrentShmem = std::move(mCanvasShmems.front());
mCanvasShmems.pop();
mCurrentMemReader = mCurrentShmem.CreateMemReader();
} }
void CanvasTranslator::ActorDestroy(ActorDestroyReason why) { void CanvasTranslator::ActorDestroy(ActorDestroyReason why) {
@ -302,6 +196,12 @@ void CanvasTranslator::ActorDestroy(ActorDestroyReason why) {
void CanvasTranslator::FinishShutdown() { void CanvasTranslator::FinishShutdown() {
MOZ_ASSERT(gfx::CanvasRenderThread::IsInCanvasRenderThread()); MOZ_ASSERT(gfx::CanvasRenderThread::IsInCanvasRenderThread());
// mTranslationTaskQueue has shutdown we can safely drop the ring buffer to
// break the cycle caused by RingBufferReaderServices.
mStream = nullptr;
gfx::CanvasManagerParent::RemoveReplayTextures(this);
} }
bool CanvasTranslator::CheckDeactivated() { bool CanvasTranslator::CheckDeactivated() {
@ -321,10 +221,10 @@ void CanvasTranslator::Deactivate() {
return; return;
} }
mDeactivated = true; mDeactivated = true;
mHeader->readerState = State::Failed;
// We need to tell the other side to deactivate. Make sure the stream is // 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. // marked as bad so that the writing side won't wait for space to write.
mStream->SetIsBad();
gfx::CanvasRenderThread::Dispatch( gfx::CanvasRenderThread::Dispatch(
NewRunnableMethod("CanvasTranslator::SendDeactivate", this, NewRunnableMethod("CanvasTranslator::SendDeactivate", this,
&CanvasTranslator::SendDeactivate)); &CanvasTranslator::SendDeactivate));
@ -338,101 +238,20 @@ void CanvasTranslator::Deactivate() {
gfx::CanvasManagerParent::DisableRemoteCanvas(); gfx::CanvasManagerParent::DisableRemoteCanvas();
} }
void CanvasTranslator::CheckAndSignalWriter() { bool CanvasTranslator::TranslateRecording() {
do {
switch (mHeader->writerState) {
case State::Processing:
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 (!CanSend()) {
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()); MOZ_ASSERT(IsInTaskQueue());
mHeader->readerState = State::Processing; if (!mStream) {
EventType eventType; return false;
while (ReadNextEvent(eventType)) { }
bool success = RecordedEvent::DoWithEvent(
mCurrentMemReader, static_cast<RecordedEvent::EventType>(eventType), uint8_t eventType = mStream->ReadNextEvent();
while (mStream->good() && eventType != kDropBufferEventType) {
bool success = RecordedEvent::DoWithEventFromStream(
*mStream, static_cast<RecordedEvent::EventType>(eventType),
[&](RecordedEvent* recordedEvent) -> bool { [&](RecordedEvent* recordedEvent) -> bool {
// Make sure that the whole event was read from the stream. // Make sure that the whole event was read from the stream.
if (!mCurrentMemReader.good()) { if (!mStream->good()) {
if (!CanSend()) { if (!CanSend()) {
// The other side has closed only warn about read failure. // The other side has closed only warn about read failure.
gfxWarning() << "Failed to read event type: " gfxWarning() << "Failed to read event type: "
@ -448,8 +267,8 @@ void CanvasTranslator::TranslateRecording() {
}); });
// Check the stream is good here or we will log the issue twice. // Check the stream is good here or we will log the issue twice.
if (!mCurrentMemReader.good()) { if (!mStream->good()) {
return; return true;
} }
if (!success && !HandleExtensionEvent(eventType)) { if (!success && !HandleExtensionEvent(eventType)) {
@ -460,16 +279,34 @@ void CanvasTranslator::TranslateRecording() {
} else { } else {
gfxCriticalNote << "Failed to play canvas event type: " << eventType; gfxCriticalNote << "Failed to play canvas event type: " << eventType;
} }
if (!mStream->good()) {
return true;
}
} }
mHeader->processedCount++; if (!mIsInTransaction) {
return mStream->StopIfEmpty();
}
if (!mStream->HasDataToRead()) {
// We're going to wait for the next event, so take the opportunity to
// flush the rendering.
Flush();
if (!mStream->WaitForDataToRead(kReadEventTimeout, 0)) {
return true;
}
}
eventType = mStream->ReadNextEvent();
} }
return true;
} }
#define READ_AND_PLAY_CANVAS_EVENT_TYPE(_typeenum, _class) \ #define READ_AND_PLAY_CANVAS_EVENT_TYPE(_typeenum, _class) \
case _typeenum: { \ case _typeenum: { \
auto e = _class(mCurrentMemReader); \ auto e = _class(*mStream); \
if (!mCurrentMemReader.good()) { \ if (!mStream->good()) { \
if (!CanSend()) { \ if (!CanSend()) { \
/* The other side has closed only warn about read failure. */ \ /* The other side has closed only warn about read failure. */ \
gfxWarning() << "Failed to read event type: " << _typeenum; \ gfxWarning() << "Failed to read event type: " << _typeenum; \
@ -638,12 +475,6 @@ already_AddRefed<gfx::SourceSurface> CanvasTranslator::LookupExternalSurface(
return SharedSurfacesParent::Get(wr::ToExternalImageId(aKey)); return SharedSurfacesParent::Get(wr::ToExternalImageId(aKey));
} }
void CanvasTranslator::CheckpointReached() { CheckAndSignalWriter(); }
void CanvasTranslator::PauseTranslation() {
mHeader->readerState = State::Paused;
}
already_AddRefed<gfx::GradientStops> CanvasTranslator::GetOrCreateGradientStops( already_AddRefed<gfx::GradientStops> CanvasTranslator::GetOrCreateGradientStops(
gfx::GradientStop* aRawStops, uint32_t aNumStops, gfx::GradientStop* aRawStops, uint32_t aNumStops,
gfx::ExtendMode aExtendMode) { gfx::ExtendMode aExtendMode) {

View file

@ -11,8 +11,6 @@
#include <vector> #include <vector>
#include "mozilla/gfx/InlineTranslator.h" #include "mozilla/gfx/InlineTranslator.h"
#include "mozilla/gfx/RecordedEvent.h"
#include "CanvasChild.h"
#include "mozilla/layers/CanvasDrawEventRecorder.h" #include "mozilla/layers/CanvasDrawEventRecorder.h"
#include "mozilla/layers/LayersSurfaces.h" #include "mozilla/layers/LayersSurfaces.h"
#include "mozilla/layers/PCanvasParent.h" #include "mozilla/layers/PCanvasParent.h"
@ -21,8 +19,6 @@
#include "mozilla/UniquePtr.h" #include "mozilla/UniquePtr.h"
namespace mozilla { namespace mozilla {
using EventType = gfx::RecordedEvent::EventType;
class TaskQueue; class TaskQueue;
namespace layers { namespace layers {
@ -56,49 +52,40 @@ class CanvasTranslator final : public gfx::InlineTranslator,
* CanvasEventRingBuffer. * CanvasEventRingBuffer.
* *
* @param aTextureType the TextureType the translator will create * @param aTextureType the TextureType the translator will create
* @param aHeaderHandle handle for the control header * @param aReadHandle handle to the shared memory for the
* @param aBufferHandles handles for the initial buffers for translation * CanvasEventRingBuffer
* @param aBufferSize size of buffers and the default size
* @param aReaderSem reading blocked semaphore for the CanvasEventRingBuffer * @param aReaderSem reading blocked semaphore for the CanvasEventRingBuffer
* @param aWriterSem writing blocked semaphore for the CanvasEventRingBuffer * @param aWriterSem writing blocked semaphore for the CanvasEventRingBuffer
* @param aUseIPDLThread if true, use the IPDL thread instead of the worker * @param aUseIPDLThread if true, use the IPDL thread instead of the worker
* pool for translation requests * pool for translation requests
*/ */
ipc::IPCResult RecvInitTranslator(const TextureType& aTextureType, ipc::IPCResult RecvInitTranslator(
Handle&& aReadHandle, const TextureType& aTextureType,
nsTArray<Handle>&& aBufferHandles, ipc::SharedMemoryBasic::Handle&& aReadHandle,
uint64_t aBufferSize, CrossProcessSemaphoreHandle&& aReaderSem,
CrossProcessSemaphoreHandle&& aReaderSem, CrossProcessSemaphoreHandle&& aWriterSem, const bool& aUseIPDLThread);
CrossProcessSemaphoreHandle&& aWriterSem,
bool aUseIPDLThread);
/** /**
* Restart the translation from a Stopped state. * New buffer to resume translation after it has been stopped by writer.
*/ */
ipc::IPCResult RecvRestartTranslation(); ipc::IPCResult RecvNewBuffer(ipc::SharedMemoryBasic::Handle&& aReadHandle);
/** /**
* Adds a new buffer to be translated. The current buffer will be recycled if * Used to tell the CanvasTranslator to start translating again after it has
* it is of the default size. The translation will then be restarted. * stopped due to a timeout waiting for events.
*/ */
ipc::IPCResult RecvAddBuffer(Handle&& aBufferHandle, uint64_t aBufferSize); ipc::IPCResult RecvResumeTranslation();
/**
* Sets the shared memory to be used for readback.
*/
ipc::IPCResult RecvSetDataSurfaceBuffer(Handle&& aBufferHandle,
uint64_t aBufferSize);
void ActorDestroy(ActorDestroyReason why) final; void ActorDestroy(ActorDestroyReason why) final;
void CheckAndSignalWriter();
/** /**
* Translates events until no more are available or the end of a transaction * Translates events until no more are available or the end of a transaction
* If this returns false the caller of this is responsible for re-calling * If this returns false the caller of this is responsible for re-calling
* this function. * this function.
*
* @returns true if all events are processed and false otherwise.
*/ */
void TranslateRecording(); bool TranslateRecording();
/** /**
* Marks the beginning of rendering for a transaction. While in a transaction * Marks the beginning of rendering for a transaction. While in a transaction
@ -123,6 +110,18 @@ class CanvasTranslator final : public gfx::InlineTranslator,
*/ */
void DeviceChangeAcknowledged(); void DeviceChangeAcknowledged();
/**
* Used to send data back to the writer. This is done through the same shared
* memory so the writer must wait and read the response after it has submitted
* the event that uses this.
*
* @param aData the data to be written back to the writer
* @param aSize the number of chars to write
*/
void ReturnWrite(const char* aData, size_t aSize) {
mStream->ReturnWrite(aData, aSize);
}
/** /**
* Set the texture ID that will be used as a lookup for the texture created by * Set the texture ID that will be used as a lookup for the texture created by
* the next CreateDrawTarget. * the next CreateDrawTarget.
@ -157,10 +156,6 @@ class CanvasTranslator final : public gfx::InlineTranslator,
*/ */
TextureData* LookupTextureData(int64_t aTextureId); TextureData* LookupTextureData(int64_t aTextureId);
void CheckpointReached();
void PauseTranslation();
/** /**
* Removes the texture and other objects associated with a texture ID. * Removes the texture and other objects associated with a texture ID.
* *
@ -249,24 +244,12 @@ class CanvasTranslator final : public gfx::InlineTranslator,
UniquePtr<gfx::DataSourceSurface::ScopedMap> GetPreparedMap( UniquePtr<gfx::DataSourceSurface::ScopedMap> GetPreparedMap(
gfx::ReferencePtr aSurface); gfx::ReferencePtr aSurface);
void RecycleBuffer();
void NextBuffer();
void GetDataSurface(uint64_t aSurfaceRef);
private: private:
~CanvasTranslator(); ~CanvasTranslator();
void AddBuffer(Handle&& aBufferHandle, size_t aBufferSize); void Bind(Endpoint<PCanvasParent>&& aEndpoint);
void SetDataSurfaceBuffer(Handle&& aBufferHandle, size_t aBufferSize); void StartTranslation();
bool ReadNextEvent(EventType& aEventType);
bool HasPendingEvent();
bool ReadPendingEvent(EventType& aEventType);
void FinishShutdown(); void FinishShutdown();
@ -290,30 +273,9 @@ class CanvasTranslator final : public gfx::InlineTranslator,
#if defined(XP_WIN) #if defined(XP_WIN)
RefPtr<ID3D11Device> mDevice; RefPtr<ID3D11Device> mDevice;
#endif #endif
// We hold the ring buffer as a UniquePtr so we can drop it once
size_t mDefaultBufferSize; // mTranslationTaskQueue has shutdown to break a RefPtr cycle.
uint32_t mMaxSpinCount; UniquePtr<CanvasEventRingBuffer> mStream;
TimeDuration mNextEventTimeout;
using State = CanvasDrawEventRecorder::State;
using Header = CanvasDrawEventRecorder::Header;
RefPtr<ipc::SharedMemoryBasic> mHeaderShmem;
Header* mHeader = nullptr;
struct CanvasShmem {
RefPtr<ipc::SharedMemoryBasic> shmem;
auto Size() { return shmem->Size(); }
MemReader CreateMemReader() {
return {static_cast<char*>(shmem->memory()), Size()};
}
};
std::queue<CanvasShmem> mCanvasShmems;
CanvasShmem mCurrentShmem;
MemReader mCurrentMemReader{0, 0};
RefPtr<ipc::SharedMemoryBasic> mDataSurfaceShmem;
UniquePtr<CrossProcessSemaphore> mWriterSemaphore;
UniquePtr<CrossProcessSemaphore> mReaderSemaphore;
TextureType mTextureType = TextureType::Unknown; TextureType mTextureType = TextureType::Unknown;
UniquePtr<TextureData> mReferenceTextureData; UniquePtr<TextureData> mReferenceTextureData;
// Sometimes during device reset our reference DrawTarget can be null, so we // Sometimes during device reset our reference DrawTarget can be null, so we

View file

@ -530,9 +530,7 @@ void CompositorBridgeChild::EndCanvasTransaction() {
} }
void CompositorBridgeChild::ClearCachedResources() { void CompositorBridgeChild::ClearCachedResources() {
if (auto* cm = gfx::CanvasManagerChild::Get()) { CanvasChild::ClearCachedResources();
cm->ClearCachedResources();
}
} }
bool CompositorBridgeChild::AllocUnsafeShmem(size_t aSize, ipc::Shmem* aShmem) { bool CompositorBridgeChild::AllocUnsafeShmem(size_t aSize, ipc::Shmem* aShmem) {

View file

@ -25,35 +25,25 @@ async protocol PCanvas {
parent: parent:
/** /**
* Initialize a CanvasTranslator for a particular TextureType, which * Initialize a CanvasTranslator for a particular TextureType, which
* translates events from shared memory buffers. aHeaderHandle is a shared * translates events from a CanvasEventRingBuffer. aReadHandle is the shared
* memory handle for the control header. aBufferHandles are shared memory * memory handle for the ring buffer. aReaderSem and aWriterSem are handles
* handles for the initial buffers for translation. aBufferSize is the size of * for the semaphores to handle waiting on either side.
* each aBufferHandles' memory and the default size. aReaderSem and aWriterSem
* are handles for the semaphores to handle waiting on either side.
* aUseIPDLThread if true, use the IPDL thread instead of the worker pool for
* translation requests
*/ */
async InitTranslator(TextureType aTextureType, Handle aHeaderHandle, async InitTranslator(TextureType aTextureType, Handle aReadHandle,
Handle[] aBufferHandles, uint64_t aBufferSize,
CrossProcessSemaphoreHandle aReaderSem, CrossProcessSemaphoreHandle aReaderSem,
CrossProcessSemaphoreHandle aWriterSem, CrossProcessSemaphoreHandle aWriterSem,
bool aUseIPDLThread); bool aUseIPDLThread);
/** /**
* Restart the translation from a Stopped state. * Send a new buffer to resume translation after it's been stopped by writer.
*/ */
async RestartTranslation(); async NewBuffer(Handle aReadHandle);
/** /**
* Adds a new buffer to be translated. The current buffer will be recycled if * Used to tell the CanvasTranslator to start translating again after it has
* it is of the default size. The translation will then be restarted. * stopped due to a timeout waiting for events.
*/ */
async AddBuffer(Handle aBufferHandle, uint64_t aBufferSize); async ResumeTranslation();
/**
* Sets the shared memory to be used for readback.
*/
async SetDataSurfaceBuffer(Handle aBufferHandle, uint64_t aBufferSize);
async __delete__(); async __delete__();

View file

@ -13,7 +13,6 @@
#include "RenderCompositorD3D11SWGL.h" #include "RenderCompositorD3D11SWGL.h"
#include "ScopedGLHelpers.h" #include "ScopedGLHelpers.h"
#include "mozilla/DebugOnly.h" #include "mozilla/DebugOnly.h"
#include "mozilla/gfx/CanvasManagerParent.h"
#include "mozilla/gfx/Logging.h" #include "mozilla/gfx/Logging.h"
#include "mozilla/layers/GpuProcessD3D11TextureMap.h" #include "mozilla/layers/GpuProcessD3D11TextureMap.h"
#include "mozilla/layers/TextureD3D11.h" #include "mozilla/layers/TextureD3D11.h"

View file

@ -5734,36 +5734,6 @@
#endif #endif
mirror: once mirror: once
# Default size of the shmem buffers used for recording
- name: gfx.canvas.remote.default-buffer-size
type: RelaxedAtomicUint32
value: 32 * 1024
mirror: always
# How many times to spin before waiting in remote canvas
- name: gfx.canvas.remote.max-spin-count
type: RelaxedAtomicUint32
value: 500
mirror: always
# How long to wait in milliseconds for the next event while in a transaction
- name: gfx.canvas.remote.event-timeout-ms
type: RelaxedAtomicUint32
value: 2
mirror: always
# How many times we have a spare buffer before we drop one
- name: gfx.canvas.remote.drop-buffer-limit
type: RelaxedAtomicUint32
value: 100
mirror: always
# Delay in milliseconds to drop buffers when there have been no non-empty transactions
- name: gfx.canvas.remote.drop-buffer-milliseconds
type: RelaxedAtomicUint32
value: 10000
mirror: always
- name: gfx.canvas.willreadfrequently.enabled - name: gfx.canvas.willreadfrequently.enabled
type: bool type: bool
#if defined(XP_WIN) #if defined(XP_WIN)