Backed out changeset 71b6060b6015 (bug 1863914) for causing Bug 1868934

This commit is contained in:
Narcis Beleuzu 2023-12-08 13:18:52 +02:00
parent 20cc3bf2c1
commit cc338968d0
19 changed files with 1125 additions and 1014 deletions

View File

@ -19,6 +19,26 @@ InlineTranslator::InlineTranslator(DrawTarget* aDT, void* aFontContext)
: mBaseDT(aDT), mFontContext(aFontContext) {}
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);
uint32_t magicInt;

View File

@ -166,28 +166,6 @@ class InlineTranslator : public Translator {
std::string GetError() { return mError; }
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;
Matrix mBaseDTTransform;
nsRefPtrHashtable<nsPtrHashKey<void>, DrawTarget> mDrawTargets;

View File

@ -25,6 +25,13 @@ bool RecordedEvent::DoWithEventFromStream(
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) {
switch (aType) {
case DRAWTARGETCREATION:

View File

@ -214,7 +214,7 @@ struct SizeCollector {
};
struct MemWriter {
constexpr explicit MemWriter(char* aPtr) : mPtr(aPtr) {}
explicit MemWriter(char* aPtr) : mPtr(aPtr) {}
void write(const char* aData, size_t aSize) {
memcpy(mPtr, aData, aSize);
mPtr += aSize;
@ -222,30 +222,13 @@ struct MemWriter {
char* mPtr;
};
class ContiguousBuffer {
public:
ContiguousBuffer(char* aStart, size_t aSize)
: 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 {
// This is a simple interface for an EventRingBuffer, so we can use it in the
// RecordedEvent reading and writing machinery.
class EventRingBuffer {
public:
/**
* Templated RecordEvent function so that we can record into the buffer
* quickly using MemWriter.
* Templated RecordEvent function so that when we have enough contiguous
* space we can record into the buffer quickly using MemWriter.
*
* @param aRecordedEvent the event to record
*/
@ -254,25 +237,56 @@ class ContiguousBufferStream {
SizeCollector size;
WriteElement(size, aRecordedEvent->GetType());
aRecordedEvent->Record(size);
auto& buffer = GetContiguousBuffer(size.mTotalSize);
if (!buffer.IsValid()) {
return;
if (size.mTotalSize > mAvailable) {
WaitForAndRecalculateAvailableSpace();
}
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:
/**
* 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 {
@ -416,7 +430,7 @@ class RecordedEvent {
virtual void RecordToStream(std::ostream& 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 OutputSimpleEventInfo(std::stringstream& aStringStream) const {}
@ -446,6 +460,9 @@ class RecordedEvent {
static bool DoWithEventFromStream(
EventStream& aStream, EventType aType,
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; }
@ -478,7 +495,7 @@ class RecordedEventDerived : public RecordedEvent {
WriteElement(aStream, this->mType);
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));
}
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() {
mActive = false;
if (mCanvasChild) {

View File

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

View File

@ -9,268 +9,294 @@
#include <string.h>
#include "mozilla/layers/SharedSurfacesChild.h"
#include "mozilla/StaticPrefs_gfx.h"
#include "RecordedCanvasEventImpl.h"
#include "nsThreadUtils.h"
namespace mozilla {
namespace layers {
struct ShmemAndHandle {
RefPtr<ipc::SharedMemoryBasic> shmem;
Handle handle;
};
static const uint32_t kMaxSpinCount = 200;
static Maybe<ShmemAndHandle> CreateAndMapShmem(size_t aSize) {
auto shmem = MakeRefPtr<ipc::SharedMemoryBasic>();
if (!shmem->Create(aSize) || !shmem->Map(aSize)) {
return Nothing();
}
static const TimeDuration kTimeout = TimeDuration::FromMilliseconds(100);
static const int32_t kTimeoutRetryCount = 50;
auto shmemHandle = shmem->TakeHandle();
if (!shmemHandle) {
return Nothing();
}
static const uint32_t kCacheLineSize = 64;
static const uint32_t kSmallStreamSize = 64 * 1024;
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() {
mDefaultBufferSize = ipc::SharedMemory::PageAlignedSize(
StaticPrefs::gfx_canvas_remote_default_buffer_size());
mMaxSpinCount = StaticPrefs::gfx_canvas_remote_max_spin_count();
mDropBufferLimit = StaticPrefs::gfx_canvas_remote_drop_buffer_limit();
mDropBufferOnZero = mDropBufferLimit;
}
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())) {
bool CanvasEventRingBuffer::InitBuffer(
base::ProcessId aOtherPid, ipc::SharedMemoryBasic::Handle* aReadHandle) {
size_t shmemSize = StreamSize() + (2 * kCacheLineSize);
mSharedMemory = MakeAndAddRef<ipc::SharedMemoryBasic>();
if (NS_WARN_IF(!mSharedMemory->Create(shmemSize)) ||
NS_WARN_IF(!mSharedMemory->Map(shmemSize))) {
mGood = false;
return false;
}
mHeader = static_cast<Header*>(header->shmem->memory());
mHeader->eventCount = 0;
mHeader->writerWaitCount = 0;
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)) {
*aReadHandle = mSharedMemory->TakeHandle();
if (NS_WARN_IF(!*aReadHandle)) {
mGood = false;
return false;
}
mReaderSemaphore.reset(CrossProcessSemaphore::Create("CanvasTranslator", 0));
auto readerSem = mReaderSemaphore->CloneHandle();
mReaderSemaphore->CloseHandle();
if (!IsHandleValid(readerSem)) {
return false;
}
mBuf = static_cast<char*>(mSharedMemory->memory());
mBufPos = mBuf;
mAvailable = StreamSize();
if (!mHelpers->InitTranslator(aTextureType, std::move(header->handle),
std::move(bufferHandles), mDefaultBufferSize,
std::move(readerSem), std::move(writerSem),
/* aUseIPDLThread */ false)) {
return false;
}
static_assert(sizeof(ReadFooter) <= kCacheLineSize,
"ReadFooter must fit in kCacheLineSize.");
mRead = reinterpret_cast<ReadFooter*>(mBuf + StreamSize());
mRead->count = 0;
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;
}
void CanvasDrawEventRecorder::RecordEvent(const gfx::RecordedEvent& aEvent) {
aEvent.RecordToStream(*this);
bool CanvasEventRingBuffer::InitWriter(
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() {
int64_t checkpoint = mHeader->eventCount;
RecordEvent(RecordedCheckpoint());
return checkpoint;
bool CanvasEventRingBuffer::InitReader(
ipc::SharedMemoryBasic::Handle aReadHandle,
CrossProcessSemaphoreHandle aReaderSem,
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) {
uint32_t spinCount = mMaxSpinCount;
bool CanvasEventRingBuffer::SetNewBuffer(
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 {
if (mHeader->processedCount >= aCheckpoint) {
return true;
memcpy(mBufPos, curDestPtr, mAvailable);
IncrementWriteCountBy(mAvailable);
curDestPtr += mAvailable;
remainingToWrite -= mAvailable;
if (!WaitForAndRecalculateAvailableSpace()) {
return;
}
} while (--spinCount != 0);
} while (remainingToWrite > mAvailable);
mHeader->writerState = State::AboutToWait;
if (mHeader->processedCount >= aCheckpoint) {
mHeader->writerState = State::Processing;
return true;
memcpy(mBufPos, curDestPtr, remainingToWrite);
UpdateWriteTotalsBy(remainingToWrite);
}
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;
mHeader->writerState = State::Waiting;
uint32_t bufPos = mOurCount % StreamSize();
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.
while (!mHelpers->ReaderClosed() && mHeader->readerState != State::Failed) {
if (mWriterSemaphore->Wait(Some(TimeDuration::FromMilliseconds(100)))) {
MOZ_ASSERT(mHeader->processedCount >= aCheckpoint);
return true;
mBufPos = mBuf + bufPos;
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
// reason (e.g. shutdown), so mark us as failed so the reader is aware.
mHeader->writerState = State::Failed;
return false;
}
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 (remainingToRead <= mAvailable) {
memcpy(curSrcPtr, mBufPos, remainingToRead);
UpdateReadTotalsBy(remainingToRead);
return;
}
// 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 {
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::Paused:
case State::Failed:
return;
case State::AboutToWait:
// 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
// closed to avoid hangs.
if (mHelpers->ReaderClosed()) {
if (mWriterServices->ReaderClosed()) {
return;
}
continue;
case State::Waiting:
if (mHeader->processedCount < mHeader->eventCount) {
if (mRead->count != mOurCount) {
// We have to use compareExchange here because the reader can change
// from Waiting to Stopped.
if (mHeader->readerState.compareExchange(State::Waiting,
State::Processing)) {
if (mRead->state.compareExchange(State::Waiting, State::Processing)) {
mReaderSemaphore->Signal();
return;
}
MOZ_ASSERT(mHeader->readerState == State::Stopped);
MOZ_ASSERT(mRead->state == State::Stopped);
continue;
}
return;
case State::Stopped:
if (mHeader->processedCount < mHeader->eventCount) {
mHeader->readerState = State::Processing;
if (!mHelpers->RestartReader()) {
mHeader->writerState = State::Failed;
}
if (mRead->count != mOurCount) {
mRead->state = State::Processing;
mWriterServices->ResumeReader();
}
return;
default:
@ -280,6 +306,267 @@ void CanvasDrawEventRecorder::CheckAndSignalReader() {
} 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(
gfx::SourceSurface* aSurface, const char* aReason) {
wr::ExternalImageId extId{};

View File

@ -7,32 +7,195 @@
#ifndef mozilla_layers_CanvasDrawEventRecorder_h
#define mozilla_layers_CanvasDrawEventRecorder_h
#include <queue>
#include "mozilla/Atomics.h"
#include "mozilla/gfx/DrawEventRecorder.h"
#include "mozilla/ipc/CrossProcessSemaphore.h"
#include "mozilla/ipc/SharedMemoryBasic.h"
#include "mozilla/layers/LayersTypes.h"
#include "mozilla/RefPtr.h"
#include "mozilla/UniquePtr.h"
namespace mozilla {
using EventType = gfx::RecordedEvent::EventType;
namespace layers {
typedef mozilla::ipc::SharedMemoryBasic::Handle Handle;
typedef mozilla::CrossProcessSemaphoreHandle CrossProcessSemaphoreHandle;
static const uint8_t kCheckpointEventType = -1;
static const uint8_t kDropBufferEventType = -2;
class CanvasDrawEventRecorder final : public gfx::DrawEventRecorderPrivate,
public gfx::ContiguousBufferStream {
class CanvasEventRingBuffer final : public gfx::EventRingBuffer {
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 {
Processing,
@ -49,61 +212,89 @@ class CanvasDrawEventRecorder final : public gfx::DrawEventRecorderPrivate,
*/
AboutToWait,
Waiting,
Paused,
Stopped,
Failed,
};
struct Header {
Atomic<int64_t> eventCount;
Atomic<int64_t> writerWaitCount;
Atomic<State> writerState;
uint8_t padding1[44];
Atomic<int64_t> processedCount;
Atomic<State> readerState;
struct ReadFooter {
Atomic<uint32_t> count;
Atomic<uint32_t> returnCount;
Atomic<State> state;
};
class Helpers {
public:
virtual ~Helpers() = default;
virtual bool InitTranslator(const TextureType& aTextureType,
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;
struct WriteFooter {
Atomic<uint32_t> count;
Atomic<uint32_t> returnCount;
Atomic<uint32_t> requiredDifference;
Atomic<State> state;
};
bool Init(TextureType aTextureType, UniquePtr<Helpers> aHelpers);
CanvasEventRingBuffer(const CanvasEventRingBuffer&) = delete;
void operator=(const CanvasEventRingBuffer&) = delete;
/**
* Record an event for processing by the CanvasParent's CanvasTranslator.
* @param aEvent the event to record
*/
void RecordEvent(const gfx::RecordedEvent& aEvent) final;
void IncrementWriteCountBy(uint32_t aCount);
bool WaitForReadCount(uint32_t aReadCount, TimeDuration aTimeout);
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,
const char* aReason) 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.
@ -112,60 +303,19 @@ class CanvasDrawEventRecorder final : public gfx::DrawEventRecorderPrivate,
* @returns true if the checkpoint was reached, false if the reader is closed
* 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();
protected:
gfx::ContiguousBuffer& GetContiguousBuffer(size_t aSize) final;
void IncrementEventCount() final;
bool SwitchBuffer(base::ProcessId aOtherPid,
ipc::SharedMemoryBasic::Handle* aHandle) {
return mOutputStream.SwitchBuffer(aOtherPid, aHandle);
}
private:
void WriteInternalEvent(EventType aEventType);
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;
CanvasEventRingBuffer mOutputStream;
};
} // 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 NEXT_TEXTURE_ID = EventType(EventType::LAST + 11);
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
: public RecordedEventDerived<RecordedCanvasBeginTransaction> {
@ -349,7 +344,31 @@ class RecordedGetDataForSurface final
inline bool RecordedGetDataForSurface::PlayCanvasEvent(
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;
}
@ -557,87 +576,6 @@ RecordedTextureDestruction::RecordedTextureDestruction(S& aStream)
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) \
f(CANVAS_BEGIN_TRANSACTION, RecordedCanvasBeginTransaction); \
f(CANVAS_END_TRANSACTION, RecordedCanvasEndTransaction); \
@ -651,11 +589,7 @@ class RecordedDropBuffer final
f(REMOVE_SURFACE_ALIAS, RecordedRemoveSurfaceAlias); \
f(DEVICE_CHANGE_ACKNOWLEDGED, RecordedDeviceChangeAcknowledged); \
f(NEXT_TEXTURE_ID, RecordedNextTextureId); \
f(TEXTURE_DESTRUCTION, RecordedTextureDestruction); \
f(CHECKPOINT, RecordedCheckpoint); \
f(PAUSE_TRANSLATION, RecordedPauseTranslation); \
f(RECYCLE_BUFFER, RecordedRecycleBuffer); \
f(DROP_BUFFER, RecordedDropBuffer);
f(TEXTURE_DESTRUCTION, RecordedTextureDestruction);
} // namespace layers
} // namespace mozilla

View File

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

View File

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

View File

@ -15,42 +15,21 @@
#include "mozilla/ipc/Endpoint.h"
#include "mozilla/ipc/ProcessChild.h"
#include "mozilla/layers/CanvasDrawEventRecorder.h"
#include "mozilla/layers/SourceSurfaceSharedData.h"
#include "mozilla/Maybe.h"
#include "nsIObserverService.h"
#include "RecordedCanvasEventImpl.h"
namespace mozilla {
namespace layers {
class RecorderHelpers final : public CanvasDrawEventRecorder::Helpers {
/* static */ bool CanvasChild::mInForeground = true;
class RingBufferWriterServices final
: public CanvasEventRingBuffer::WriterServices {
public:
explicit RecorderHelpers(const RefPtr<CanvasChild>& aCanvasChild)
explicit RingBufferWriterServices(RefPtr<CanvasChild> aCanvasChild)
: mCanvasChild(aCanvasChild) {}
~RecorderHelpers() 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);
}
~RingBufferWriterServices() override = default;
bool ReaderClosed() override {
if (!mCanvasChild) {
@ -59,11 +38,11 @@ class RecorderHelpers final : public CanvasDrawEventRecorder::Helpers {
return !mCanvasChild->CanSend() || ipc::ProcessChild::ExpectingShutdown();
}
bool RestartReader() override {
void ResumeReader() override {
if (!mCanvasChild) {
return false;
return;
}
return mCanvasChild->SendRestartTranslation();
mCanvasChild->ResumeTranslation();
}
private:
@ -176,24 +155,45 @@ ipc::IPCResult CanvasChild::RecvDeactivate() {
return IPC_OK();
}
void CanvasChild::EnsureRecorder(gfx::IntSize aSize, gfx::SurfaceFormat aFormat,
TextureType aTextureType) {
void CanvasChild::EnsureRecorder(TextureType aTextureType) {
if (!mRecorder) {
auto recorder = MakeRefPtr<CanvasDrawEventRecorder>();
if (!recorder->Init(aTextureType, MakeUnique<RecorderHelpers>(this))) {
MOZ_ASSERT(mTextureType == TextureType::Unknown);
mTextureType = aTextureType;
mRecorder = MakeAndAddRef<CanvasDrawEventRecorder>();
SharedMemoryBasic::Handle handle;
CrossProcessSemaphoreHandle readerSem;
CrossProcessSemaphoreHandle writerSem;
if (!mRecorder->Init(OtherPid(), &handle, &readerSem, &writerSem,
MakeUnique<RingBufferWriterServices>(this))) {
mRecorder = nullptr;
return;
}
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.");
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() {
if (CanSend()) {
@ -202,11 +202,24 @@ void CanvasChild::Destroy() {
}
void CanvasChild::OnTextureWriteLock() {
// We drop mRecorder in ActorDestroy to break the reference cycle.
if (!mRecorder) {
return;
}
mHasOutstandingWriteLock = true;
mLastWriteLockCheckpoint = mRecorder->CreateCheckpoint();
}
void CanvasChild::OnTextureForwarded() {
// We drop mRecorder in ActorDestroy to break the reference cycle.
if (!mRecorder) {
return;
}
// We're forwarding textures, so we must be in the foreground.
mInForeground = true;
if (mHasOutstandingWriteLock) {
mRecorder->RecordEvent(RecordedCanvasFlush());
if (!mRecorder->WaitForCheckpoint(mLastWriteLockCheckpoint)) {
@ -225,8 +238,24 @@ void CanvasChild::OnTextureForwarded() {
}
bool CanvasChild::EnsureBeginTransaction() {
// We drop mRecorder in ActorDestroy to break the reference cycle.
if (!mRecorder) {
return false;
}
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;
}
@ -234,33 +263,25 @@ bool CanvasChild::EnsureBeginTransaction() {
}
void CanvasChild::EndTransaction() {
// We drop mRecorder in ActorDestroy to break the reference cycle.
if (!mRecorder) {
return;
}
if (mIsInTransaction) {
RecordEvent(RecordedCanvasEndTransaction());
mRecorder->RecordEvent(RecordedCanvasEndTransaction());
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;
}
void CanvasChild::DropFreeBuffersWhenDormant() {
// Drop any free buffers if we have not had any non-empty transactions.
if (mDormant) {
mRecorder->DropFreeBuffers();
}
/* static */
void CanvasChild::ClearCachedResources() {
// We use this as a proxy for the tab being in the backgound.
mInForeground = false;
}
void CanvasChild::ClearCachedResources() { mRecorder->DropFreeBuffers(); }
bool CanvasChild::ShouldBeCleanedUp() const {
// Always return true if we've been deactivated.
if (Deactivated()) {
@ -268,11 +289,16 @@ bool CanvasChild::ShouldBeCleanedUp() const {
}
// 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(
gfx::IntSize aSize, gfx::SurfaceFormat aFormat) {
// We drop mRecorder in ActorDestroy to break the reference cycle.
if (!mRecorder) {
return nullptr;
}
RefPtr<gfx::DrawTarget> dummyDt = gfx::Factory::CreateDrawTarget(
gfx::BackendType::SKIA, gfx::IntSize(1, 1), aFormat);
RefPtr<gfx::DrawTarget> dt = MakeAndAddRef<gfx::DrawTargetRecording>(
@ -280,36 +306,6 @@ already_AddRefed<gfx::DrawTarget> CanvasChild::CreateDrawTarget(
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 (!mDataSurfaceShmemAvailable || mDataSurfaceShmem->Size() < sizeRequired) {
RecordEvent(RecordedPauseTranslation());
auto dataSurfaceShmem = MakeRefPtr<ipc::SharedMemoryBasic>();
if (!dataSurfaceShmem->Create(sizeRequired) ||
!dataSurfaceShmem->Map(sizeRequired)) {
return false;
}
auto shmemHandle = dataSurfaceShmem->TakeHandle();
if (!shmemHandle) {
return false;
}
if (!SendSetDataSurfaceBuffer(std::move(shmemHandle), sizeRequired)) {
return false;
}
mDataSurfaceShmem = dataSurfaceShmem.forget();
mDataSurfaceShmemAvailable = true;
}
MOZ_ASSERT(mDataSurfaceShmemAvailable);
return true;
}
void CanvasChild::RecordEvent(const gfx::RecordedEvent& aEvent) {
// We drop mRecorder in ActorDestroy to break the reference cycle.
if (!mRecorder) {
@ -319,15 +315,16 @@ void CanvasChild::RecordEvent(const gfx::RecordedEvent& aEvent) {
mRecorder->RecordEvent(aEvent);
}
int64_t CanvasChild::CreateCheckpoint() {
return mRecorder->CreateCheckpoint();
}
already_AddRefed<gfx::DataSourceSurface> CanvasChild::GetDataSurface(
const gfx::SourceSurface* aSurface) {
MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aSurface);
// We drop mRecorder in ActorDestroy to break the reference cycle.
if (!mRecorder) {
return nullptr;
}
// mTransactionsSinceGetDataSurface is used to determine if we want to prepare
// a DataSourceSurface in the GPU process up front at the end of the
// transaction, but that only makes sense if the canvas JS is requesting data
@ -340,59 +337,43 @@ already_AddRefed<gfx::DataSourceSurface> CanvasChild::GetDataSurface(
return nullptr;
}
RecordEvent(RecordedPrepareDataForSurface(aSurface));
mRecorder->RecordEvent(RecordedPrepareDataForSurface(aSurface));
uint32_t checkpoint = mRecorder->CreateCheckpoint();
gfx::IntSize ssSize = aSurface->GetSize();
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;
}
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();
}
mDataSurfaceShmemAvailable = false;
RecordEvent(RecordedGetDataForSurface(aSurface));
auto checkpoint = CreateCheckpoint();
struct DataShmemHolder {
RefPtr<ipc::SharedMemoryBasic> shmem;
RefPtr<CanvasChild> canvasChild;
};
mRecorder->RecordEvent(RecordedGetDataForSurface(aSurface));
mRecorder->ReturnRead(dest, ssSize.height * dataFormatWidth);
auto* data = static_cast<uint8_t*>(mDataSurfaceShmem->memory());
auto* closure = new DataShmemHolder{do_AddRef(mDataSurfaceShmem), 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();
}
already_AddRefed<gfx::SourceSurface> CanvasChild::WrapSurface(
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 MakeAndAddRef<SourceSurfaceCanvasRecording>(aSurface, this, mRecorder);
}
void CanvasChild::ReturnDataSurfaceShmem(
already_AddRefed<ipc::SharedMemoryBasic> aDataSurfaceShmem) {
RefPtr<ipc::SharedMemoryBasic> data = aDataSurfaceShmem;
// We can only reuse the latest data surface shmem.
if (data == mDataSurfaceShmem) {
MOZ_ASSERT(!mDataSurfaceShmemAvailable);
mDataSurfaceShmemAvailable = true;
}
}
} // namespace layers
} // namespace mozilla

View File

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

View File

@ -14,10 +14,8 @@
#include "mozilla/gfx/GPUParent.h"
#include "mozilla/gfx/Logging.h"
#include "mozilla/ipc/Endpoint.h"
#include "mozilla/layers/CanvasTranslator.h"
#include "mozilla/layers/SharedSurfacesParent.h"
#include "mozilla/layers/TextureClient.h"
#include "mozilla/StaticPrefs_gfx.h"
#include "mozilla/SyncRunnable.h"
#include "mozilla/TaskQueue.h"
#include "mozilla/Telemetry.h"
@ -31,6 +29,25 @@
namespace mozilla {
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,
const gfx::IntSize& aSize,
gfx::SurfaceFormat aFormat) {
@ -51,10 +68,6 @@ TextureData* CanvasTranslator::CreateTextureData(TextureType aTextureType,
}
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.
Telemetry::ScalarAdd(Telemetry::ScalarID::GFX_CANVAS_REMOTE_ACTIVATED, 1);
}
@ -82,49 +95,31 @@ bool CanvasTranslator::IsInTaskQueue() const {
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(
const TextureType& aTextureType, Handle&& aReadHandle,
nsTArray<Handle>&& aBufferHandles, uint64_t aBufferSize,
const TextureType& aTextureType,
ipc::SharedMemoryBasic::Handle&& aReadHandle,
CrossProcessSemaphoreHandle&& aReaderSem,
CrossProcessSemaphoreHandle&& aWriterSem, bool aUseIPDLThread) {
if (mHeaderShmem) {
CrossProcessSemaphoreHandle&& aWriterSem, const bool& aUseIPDLThread) {
if (mStream) {
return IPC_FAIL(this, "RecvInitTranslator called twice.");
}
mTextureType = aTextureType;
mHeaderShmem = MakeAndAddRef<ipc::SharedMemoryBasic>();
if (!CreateAndMapShmem(mHeaderShmem, std::move(aReadHandle),
ipc::SharedMemory::RightsReadWrite, sizeof(Header))) {
return IPC_FAIL(this, "Failed.");
// We need to initialize the stream first, because it might be used to
// communicate other failures back to the writer.
mStream = MakeUnique<CanvasEventRingBuffer>();
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 (!CheckForFreshCanvasDevice(__LINE__)) {
gfxCriticalNote << "GFX: CanvasTranslator failed to get device";
mStream = nullptr;
return IPC_OK();
}
#endif
@ -132,157 +127,56 @@ mozilla::ipc::IPCResult CanvasTranslator::RecvInitTranslator(
if (!aUseIPDLThread) {
mTranslationTaskQueue = gfx::CanvasRenderThread::CreateWorkerTaskQueue();
}
// 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();
return RecvResumeTranslation();
}
ipc::IPCResult CanvasTranslator::RecvRestartTranslation() {
if (mDeactivated) {
// The other side might have sent a message before we deactivated.
return IPC_OK();
ipc::IPCResult CanvasTranslator::RecvNewBuffer(
ipc::SharedMemoryBasic::Handle&& aReadHandle) {
if (!mStream) {
return IPC_FAIL(this, "RecvNewBuffer before RecvInitTranslator.");
}
DispatchToTaskQueue(NewRunnableMethod("CanvasTranslator::TranslateRecording",
this,
&CanvasTranslator::TranslateRecording));
return IPC_OK();
// We need to set the new buffer on the translation queue to be sure that the
// drop buffer event has been processed.
DispatchToTaskQueue(NS_NewRunnableFunction(
"CanvasTranslator SetNewBuffer",
[self = RefPtr(this), readHandle = std::move(aReadHandle)]() mutable {
self->mStream->SetNewBuffer(std::move(readHandle));
}));
return RecvResumeTranslation();
}
ipc::IPCResult CanvasTranslator::RecvAddBuffer(
ipc::SharedMemoryBasic::Handle&& aBufferHandle, uint64_t aBufferSize) {
if (mDeactivated) {
ipc::IPCResult CanvasTranslator::RecvResumeTranslation() {
if (!mStream) {
return IPC_FAIL(this, "RecvResumeTranslation before RecvInitTranslator.");
}
if (CheckDeactivated()) {
// The other side might have sent a resume message before we deactivated.
return IPC_OK();
}
DispatchToTaskQueue(
NewRunnableMethod<ipc::SharedMemoryBasic::Handle&&, size_t>(
"CanvasTranslator::AddBuffer", this, &CanvasTranslator::AddBuffer,
std::move(aBufferHandle), aBufferSize));
DispatchToTaskQueue(NewRunnableMethod("CanvasTranslator::StartTranslation",
this,
&CanvasTranslator::StartTranslation));
return IPC_OK();
}
void CanvasTranslator::AddBuffer(ipc::SharedMemoryBasic::Handle&& aBufferHandle,
size_t aBufferSize) {
MOZ_ASSERT(IsInTaskQueue());
MOZ_RELEASE_ASSERT(mHeader->readerState == State::Paused);
void CanvasTranslator::StartTranslation() {
MOZ_RELEASE_ASSERT(mStream->IsValid(),
"StartTranslation called before buffer has been set.");
// Default sized buffers will have been queued for recycling.
if (mCurrentShmem.Size() == mDefaultBufferSize) {
mCanvasShmems.emplace(std::move(mCurrentShmem));
if (!TranslateRecording() && CanSend()) {
DispatchToTaskQueue(NewRunnableMethod("CanvasTranslator::StartTranslation",
this,
&CanvasTranslator::StartTranslation));
}
CanvasShmem newShmem;
if (!CreateAndMapShmem(newShmem.shmem, std::move(aBufferHandle),
ipc::SharedMemory::RightsReadOnly, aBufferSize)) {
return;
// If the stream has been marked as bad and the Writer hasn't failed,
// deactivate remote canvas.
if (!mStream->good() && !mStream->WriterFailed()) {
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) {
@ -302,6 +196,12 @@ void CanvasTranslator::ActorDestroy(ActorDestroyReason why) {
void CanvasTranslator::FinishShutdown() {
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() {
@ -321,10 +221,10 @@ void CanvasTranslator::Deactivate() {
return;
}
mDeactivated = true;
mHeader->readerState = State::Failed;
// 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.
mStream->SetIsBad();
gfx::CanvasRenderThread::Dispatch(
NewRunnableMethod("CanvasTranslator::SendDeactivate", this,
&CanvasTranslator::SendDeactivate));
@ -338,101 +238,20 @@ void CanvasTranslator::Deactivate() {
gfx::CanvasManagerParent::DisableRemoteCanvas();
}
void CanvasTranslator::CheckAndSignalWriter() {
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() {
bool CanvasTranslator::TranslateRecording() {
MOZ_ASSERT(IsInTaskQueue());
mHeader->readerState = State::Processing;
EventType eventType;
while (ReadNextEvent(eventType)) {
bool success = RecordedEvent::DoWithEvent(
mCurrentMemReader, static_cast<RecordedEvent::EventType>(eventType),
if (!mStream) {
return false;
}
uint8_t eventType = mStream->ReadNextEvent();
while (mStream->good() && eventType != kDropBufferEventType) {
bool success = RecordedEvent::DoWithEventFromStream(
*mStream, static_cast<RecordedEvent::EventType>(eventType),
[&](RecordedEvent* recordedEvent) -> bool {
// Make sure that the whole event was read from the stream.
if (!mCurrentMemReader.good()) {
if (!mStream->good()) {
if (!CanSend()) {
// The other side has closed only warn about read failure.
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.
if (!mCurrentMemReader.good()) {
return;
if (!mStream->good()) {
return true;
}
if (!success && !HandleExtensionEvent(eventType)) {
@ -460,16 +279,34 @@ void CanvasTranslator::TranslateRecording() {
} else {
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) \
case _typeenum: { \
auto e = _class(mCurrentMemReader); \
if (!mCurrentMemReader.good()) { \
auto e = _class(*mStream); \
if (!mStream->good()) { \
if (!CanSend()) { \
/* The other side has closed only warn about read failure. */ \
gfxWarning() << "Failed to read event type: " << _typeenum; \
@ -638,12 +475,6 @@ already_AddRefed<gfx::SourceSurface> CanvasTranslator::LookupExternalSurface(
return SharedSurfacesParent::Get(wr::ToExternalImageId(aKey));
}
void CanvasTranslator::CheckpointReached() { CheckAndSignalWriter(); }
void CanvasTranslator::PauseTranslation() {
mHeader->readerState = State::Paused;
}
already_AddRefed<gfx::GradientStops> CanvasTranslator::GetOrCreateGradientStops(
gfx::GradientStop* aRawStops, uint32_t aNumStops,
gfx::ExtendMode aExtendMode) {

View File

@ -11,8 +11,6 @@
#include <vector>
#include "mozilla/gfx/InlineTranslator.h"
#include "mozilla/gfx/RecordedEvent.h"
#include "CanvasChild.h"
#include "mozilla/layers/CanvasDrawEventRecorder.h"
#include "mozilla/layers/LayersSurfaces.h"
#include "mozilla/layers/PCanvasParent.h"
@ -21,8 +19,6 @@
#include "mozilla/UniquePtr.h"
namespace mozilla {
using EventType = gfx::RecordedEvent::EventType;
class TaskQueue;
namespace layers {
@ -56,49 +52,40 @@ class CanvasTranslator final : public gfx::InlineTranslator,
* CanvasEventRingBuffer.
*
* @param aTextureType the TextureType the translator will create
* @param aHeaderHandle handle for the control header
* @param aBufferHandles handles for the initial buffers for translation
* @param aBufferSize size of buffers and the default size
* @param aReadHandle handle to the shared memory for the
* CanvasEventRingBuffer
* @param aReaderSem reading 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
* pool for translation requests
*/
ipc::IPCResult RecvInitTranslator(const TextureType& aTextureType,
Handle&& aReadHandle,
nsTArray<Handle>&& aBufferHandles,
uint64_t aBufferSize,
CrossProcessSemaphoreHandle&& aReaderSem,
CrossProcessSemaphoreHandle&& aWriterSem,
bool aUseIPDLThread);
ipc::IPCResult RecvInitTranslator(
const TextureType& aTextureType,
ipc::SharedMemoryBasic::Handle&& aReadHandle,
CrossProcessSemaphoreHandle&& aReaderSem,
CrossProcessSemaphoreHandle&& aWriterSem, const 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
* it is of the default size. The translation will then be restarted.
* Used to tell the CanvasTranslator to start translating again after it has
* stopped due to a timeout waiting for events.
*/
ipc::IPCResult RecvAddBuffer(Handle&& aBufferHandle, uint64_t aBufferSize);
/**
* Sets the shared memory to be used for readback.
*/
ipc::IPCResult RecvSetDataSurfaceBuffer(Handle&& aBufferHandle,
uint64_t aBufferSize);
ipc::IPCResult RecvResumeTranslation();
void ActorDestroy(ActorDestroyReason why) final;
void CheckAndSignalWriter();
/**
* 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
* 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
@ -123,6 +110,18 @@ class CanvasTranslator final : public gfx::InlineTranslator,
*/
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
* the next CreateDrawTarget.
@ -157,10 +156,6 @@ class CanvasTranslator final : public gfx::InlineTranslator,
*/
TextureData* LookupTextureData(int64_t aTextureId);
void CheckpointReached();
void PauseTranslation();
/**
* 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(
gfx::ReferencePtr aSurface);
void RecycleBuffer();
void NextBuffer();
void GetDataSurface(uint64_t aSurfaceRef);
private:
~CanvasTranslator();
void AddBuffer(Handle&& aBufferHandle, size_t aBufferSize);
void Bind(Endpoint<PCanvasParent>&& aEndpoint);
void SetDataSurfaceBuffer(Handle&& aBufferHandle, size_t aBufferSize);
bool ReadNextEvent(EventType& aEventType);
bool HasPendingEvent();
bool ReadPendingEvent(EventType& aEventType);
void StartTranslation();
void FinishShutdown();
@ -290,30 +273,9 @@ class CanvasTranslator final : public gfx::InlineTranslator,
#if defined(XP_WIN)
RefPtr<ID3D11Device> mDevice;
#endif
size_t mDefaultBufferSize;
uint32_t mMaxSpinCount;
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;
// We hold the ring buffer as a UniquePtr so we can drop it once
// mTranslationTaskQueue has shutdown to break a RefPtr cycle.
UniquePtr<CanvasEventRingBuffer> mStream;
TextureType mTextureType = TextureType::Unknown;
UniquePtr<TextureData> mReferenceTextureData;
// Sometimes during device reset our reference DrawTarget can be null, so we

View File

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

View File

@ -25,35 +25,25 @@ async protocol PCanvas {
parent:
/**
* Initialize a CanvasTranslator for a particular TextureType, which
* translates events from shared memory buffers. aHeaderHandle is a shared
* memory handle for the control header. aBufferHandles are shared memory
* handles for the initial buffers for translation. aBufferSize is the size of
* 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
* translates events from a CanvasEventRingBuffer. aReadHandle is the shared
* memory handle for the ring buffer. aReaderSem and aWriterSem are handles
* for the semaphores to handle waiting on either side.
*/
async InitTranslator(TextureType aTextureType, Handle aHeaderHandle,
Handle[] aBufferHandles, uint64_t aBufferSize,
async InitTranslator(TextureType aTextureType, Handle aReadHandle,
CrossProcessSemaphoreHandle aReaderSem,
CrossProcessSemaphoreHandle aWriterSem,
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
* it is of the default size. The translation will then be restarted.
* Used to tell the CanvasTranslator to start translating again after it has
* stopped due to a timeout waiting for events.
*/
async AddBuffer(Handle aBufferHandle, uint64_t aBufferSize);
/**
* Sets the shared memory to be used for readback.
*/
async SetDataSurfaceBuffer(Handle aBufferHandle, uint64_t aBufferSize);
async ResumeTranslation();
async __delete__();

View File

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

View File

@ -5744,36 +5744,6 @@
#endif
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
type: bool
#if defined(XP_WIN)