diff options
Diffstat (limited to 'gfx/layers/CanvasDrawEventRecorder.cpp')
-rw-r--r-- | gfx/layers/CanvasDrawEventRecorder.cpp | 518 |
1 files changed, 518 insertions, 0 deletions
diff --git a/gfx/layers/CanvasDrawEventRecorder.cpp b/gfx/layers/CanvasDrawEventRecorder.cpp new file mode 100644 index 0000000000..e797ad72aa --- /dev/null +++ b/gfx/layers/CanvasDrawEventRecorder.cpp @@ -0,0 +1,518 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "CanvasDrawEventRecorder.h" + +#include <string.h> + +#include "mozilla/layers/SharedSurfacesChild.h" +#include "nsThreadUtils.h" + +namespace mozilla { +namespace layers { + +static const int32_t kCheckpointEventType = -1; +static const uint32_t kMaxSpinCount = 200; + +static const TimeDuration kTimeout = TimeDuration::FromMilliseconds(100); +static const int32_t kTimeoutRetryCount = 50; + +static const uint32_t kCacheLineSize = 64; +static const uint32_t kStreamSize = 64 * 1024; +static const uint32_t kShmemSize = kStreamSize + (2 * kCacheLineSize); + +static_assert((static_cast<uint64_t>(UINT32_MAX) + 1) % kStreamSize == 0, + "kStreamSize must be a power of two."); + +bool CanvasEventRingBuffer::InitWriter( + base::ProcessId aOtherPid, ipc::SharedMemoryBasic::Handle* aReadHandle, + CrossProcessSemaphoreHandle* aReaderSem, + CrossProcessSemaphoreHandle* aWriterSem, + UniquePtr<WriterServices> aWriterServices) { + mSharedMemory = MakeAndAddRef<ipc::SharedMemoryBasic>(); + if (NS_WARN_IF(!mSharedMemory->Create(kShmemSize)) || + NS_WARN_IF(!mSharedMemory->Map(kShmemSize))) { + return false; + } + + *aReadHandle = mSharedMemory->CloneHandle(); + if (NS_WARN_IF(!*aReadHandle)) { + return false; + } + + mSharedMemory->CloseHandle(); + + mBuf = static_cast<char*>(mSharedMemory->memory()); + mBufPos = mBuf; + mAvailable = kStreamSize; + + static_assert(sizeof(ReadFooter) <= kCacheLineSize, + "ReadFooter must fit in kCacheLineSize."); + mRead = reinterpret_cast<ReadFooter*>(mBuf + kStreamSize); + 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 + kStreamSize + kCacheLineSize); + mWrite->count = 0; + mWrite->returnCount = 0; + mWrite->requiredDifference = 0; + mWrite->state = State::Processing; + + 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; +} + +bool CanvasEventRingBuffer::InitReader( + ipc::SharedMemoryBasic::Handle aReadHandle, + CrossProcessSemaphoreHandle aReaderSem, + CrossProcessSemaphoreHandle aWriterSem, + UniquePtr<ReaderServices> aReaderServices) { + mSharedMemory = MakeAndAddRef<ipc::SharedMemoryBasic>(); + if (NS_WARN_IF(!mSharedMemory->SetHandle( + std::move(aReadHandle), ipc::SharedMemory::RightsReadWrite)) || + NS_WARN_IF(!mSharedMemory->Map(kShmemSize))) { + return false; + } + + mSharedMemory->CloseHandle(); + + mBuf = static_cast<char*>(mSharedMemory->memory()); + mRead = reinterpret_cast<ReadFooter*>(mBuf + kStreamSize); + mWrite = reinterpret_cast<WriteFooter*>(mBuf + kStreamSize + kCacheLineSize); + 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 CanvasEventRingBuffer::WaitForAndRecalculateAvailableSpace() { + if (!good()) { + return false; + } + + uint32_t bufPos = mOurCount % kStreamSize; + uint32_t maxToWrite = kStreamSize - 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 { + memcpy(mBufPos, curDestPtr, mAvailable); + IncrementWriteCountBy(mAvailable); + curDestPtr += mAvailable; + remainingToWrite -= mAvailable; + if (!WaitForAndRecalculateAvailableSpace()) { + return; + } + } while (remainingToWrite > mAvailable); + + 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; + } + + uint32_t bufPos = mOurCount % kStreamSize; + uint32_t maxToRead = kStreamSize - bufPos; + mAvailable = std::min(maxToRead, WaitForBytesToRead()); + if (!mAvailable) { + SetIsBad(); + mBufPos = nullptr; + return false; + } + + 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; + } + } + + if (remainingToRead <= mAvailable) { + memcpy(curSrcPtr, mBufPos, remainingToRead); + UpdateReadTotalsBy(remainingToRead); + return; + } + + do { + 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::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 (mWriterServices->ReaderClosed()) { + return; + } + continue; + case State::Waiting: + if (mRead->count != mOurCount) { + // We have to use compareExchange here because the reader can change + // from Waiting to Stopped. + if (mRead->state.compareExchange(State::Waiting, State::Processing)) { + mReaderSemaphore->Signal(); + return; + } + + MOZ_ASSERT(mRead->state == State::Stopped); + continue; + } + return; + case State::Stopped: + if (mRead->count != mOurCount) { + mRead->state = State::Processing; + mWriterServices->ResumeReader(); + } + return; + default: + MOZ_ASSERT_UNREACHABLE("Invalid waiting state."); + return; + } + } 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; +} + +int32_t CanvasEventRingBuffer::ReadNextEvent() { + int32_t nextEvent; + ReadElement(*this, nextEvent); + while (nextEvent == kCheckpointEventType && good()) { + ReadElement(*this, nextEvent); + } + + return nextEvent; +} + +uint32_t CanvasEventRingBuffer::CreateCheckpoint() { + WriteElement(*this, kCheckpointEventType); + return mOurCount; +} + +bool CanvasEventRingBuffer::WaitForCheckpoint(uint32_t aCheckpoint) { + return WaitForReadCount(aCheckpoint, kTimeout); +} + +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 - kStreamSize; + 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 % kStreamSize; + uint32_t bufRemaining = kStreamSize - bufPos; + uint32_t availableToWrite = + std::min(bufRemaining, (mWrite->returnCount + kStreamSize - writeCount)); + while (availableToWrite < aSize) { + if (availableToWrite) { + memcpy(mBuf + bufPos, aData, availableToWrite); + writeCount += availableToWrite; + mRead->returnCount = writeCount; + bufPos = writeCount % kStreamSize; + bufRemaining = kStreamSize - bufPos; + aData += availableToWrite; + aSize -= availableToWrite; + } else if (mReaderServices->WriterClosed()) { + return; + } + + availableToWrite = std::min( + bufRemaining, (mWrite->returnCount + kStreamSize - 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 % kStreamSize; + uint32_t bufRemaining = kStreamSize - 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 % kStreamSize; + bufRemaining = kStreamSize - 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{}; + nsresult rv = layers::SharedSurfacesChild::Share(aSurface, extId); + if (NS_FAILED(rv)) { + DrawEventRecorderPrivate::StoreSourceSurfaceRecording(aSurface, aReason); + return; + } + + StoreExternalSurfaceRecording(aSurface, wr::AsUint64(extId)); +} + +} // namespace layers +} // namespace mozilla |