diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /gfx/layers/CanvasDrawEventRecorder.cpp | |
parent | Initial commit. (diff) | |
download | firefox-upstream/124.0.1.tar.xz firefox-upstream/124.0.1.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'gfx/layers/CanvasDrawEventRecorder.cpp')
-rw-r--r-- | gfx/layers/CanvasDrawEventRecorder.cpp | 347 |
1 files changed, 347 insertions, 0 deletions
diff --git a/gfx/layers/CanvasDrawEventRecorder.cpp b/gfx/layers/CanvasDrawEventRecorder.cpp new file mode 100644 index 0000000000..1bf928b816 --- /dev/null +++ b/gfx/layers/CanvasDrawEventRecorder.cpp @@ -0,0 +1,347 @@ +/* -*- 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 "mozilla/StaticPrefs_gfx.h" +#include "RecordedCanvasEventImpl.h" + +namespace mozilla { +namespace layers { + +struct ShmemAndHandle { + RefPtr<ipc::SharedMemoryBasic> shmem; + Handle handle; +}; + +static Maybe<ShmemAndHandle> CreateAndMapShmem(size_t aSize) { + auto shmem = MakeRefPtr<ipc::SharedMemoryBasic>(); + if (!shmem->Create(aSize) || !shmem->Map(aSize)) { + return Nothing(); + } + + auto shmemHandle = shmem->TakeHandle(); + if (!shmemHandle) { + return Nothing(); + } + + return Some(ShmemAndHandle{shmem.forget(), std::move(shmemHandle)}); +} + +CanvasDrawEventRecorder::CanvasDrawEventRecorder() { + mDefaultBufferSize = ipc::SharedMemory::PageAlignedSize( + StaticPrefs::gfx_canvas_remote_default_buffer_size()); + mMaxDefaultBuffers = StaticPrefs::gfx_canvas_remote_max_default_buffers(); + mMaxSpinCount = StaticPrefs::gfx_canvas_remote_max_spin_count(); + mDropBufferLimit = StaticPrefs::gfx_canvas_remote_drop_buffer_limit(); + mDropBufferOnZero = mDropBufferLimit; +} + +bool CanvasDrawEventRecorder::Init(TextureType aTextureType, + gfx::BackendType aBackendType, + UniquePtr<Helpers> aHelpers) { + NS_ASSERT_OWNINGTHREAD(CanvasDrawEventRecorder); + + mHelpers = std::move(aHelpers); + + MOZ_ASSERT(mTextureType == TextureType::Unknown); + auto header = CreateAndMapShmem(sizeof(Header)); + if (NS_WARN_IF(header.isNothing())) { + 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::Paused; + + // We always keep at least two buffers. This means that when we + // have to add a new buffer, there is at least a full buffer that requires + // translating while the handle is sent over. + AutoTArray<Handle, 2> bufferHandles; + auto buffer = CreateAndMapShmem(mDefaultBufferSize); + if (NS_WARN_IF(buffer.isNothing())) { + return false; + } + mCurrentBuffer = CanvasBuffer(std::move(buffer->shmem)); + bufferHandles.AppendElement(std::move(buffer->handle)); + + buffer = CreateAndMapShmem(mDefaultBufferSize); + if (NS_WARN_IF(buffer.isNothing())) { + return false; + } + mRecycledBuffers.emplace(buffer->shmem.forget(), 0); + bufferHandles.AppendElement(std::move(buffer->handle)); + + mWriterSemaphore.reset(CrossProcessSemaphore::Create("CanvasRecorder", 0)); + auto writerSem = mWriterSemaphore->CloneHandle(); + mWriterSemaphore->CloseHandle(); + if (!IsHandleValid(writerSem)) { + return false; + } + + mReaderSemaphore.reset(CrossProcessSemaphore::Create("CanvasTranslator", 0)); + auto readerSem = mReaderSemaphore->CloneHandle(); + mReaderSemaphore->CloseHandle(); + if (!IsHandleValid(readerSem)) { + return false; + } + + if (!mHelpers->InitTranslator(aTextureType, aBackendType, + std::move(header->handle), + std::move(bufferHandles), mDefaultBufferSize, + std::move(readerSem), std::move(writerSem))) { + return false; + } + + mTextureType = aTextureType; + mHeaderShmem = header->shmem; + return true; +} + +void CanvasDrawEventRecorder::RecordEvent(const gfx::RecordedEvent& aEvent) { + NS_ASSERT_OWNINGTHREAD(CanvasDrawEventRecorder); + aEvent.RecordToStream(*this); +} + +int64_t CanvasDrawEventRecorder::CreateCheckpoint() { + NS_ASSERT_OWNINGTHREAD(CanvasDrawEventRecorder); + int64_t checkpoint = mHeader->eventCount; + RecordEvent(RecordedCheckpoint()); + ClearProcessedExternalSurfaces(); + return checkpoint; +} + +bool CanvasDrawEventRecorder::WaitForCheckpoint(int64_t aCheckpoint) { + NS_ASSERT_OWNINGTHREAD(CanvasDrawEventRecorder); + + uint32_t spinCount = mMaxSpinCount; + do { + if (mHeader->processedCount >= aCheckpoint) { + return true; + } + } while (--spinCount != 0); + + mHeader->writerState = State::AboutToWait; + if (mHeader->processedCount >= aCheckpoint) { + mHeader->writerState = State::Processing; + return true; + } + + mHeader->writerWaitCount = aCheckpoint; + mHeader->writerState = State::Waiting; + + // 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; + } + } + + // 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) { + if (!mCurrentBuffer.IsValid()) { + // If the current buffer is invalid then we've already failed previously. + MOZ_ASSERT(mHeader->writerState == State::Failed); + return mCurrentBuffer; + } + + // 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; + } + + bool useRecycledBuffer = false; + if (mRecycledBuffers.front().Capacity() > aSize) { + // The recycled buffer is big enough, check if it is free. + if (mRecycledBuffers.front().eventCount <= mHeader->processedCount) { + useRecycledBuffer = true; + } else if (mRecycledBuffers.size() >= mMaxDefaultBuffers) { + // We've hit he max number of buffers, wait for the next one to be free. + // We wait for (eventCount - 1), as we check and signal in the translator + // during the play event, before the processedCount has been updated. + useRecycledBuffer = true; + if (!WaitForCheckpoint(mRecycledBuffers.front().eventCount - 1)) { + // The wait failed or we're shutting down, just return an empty buffer. + mCurrentBuffer = CanvasBuffer(); + return mCurrentBuffer; + } + } + } + + if (useRecycledBuffer) { + // 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(); + return mCurrentBuffer; + } + + if (!mHelpers->AddBuffer(std::move(newBuffer->handle), bufferSize)) { + mHeader->writerState = State::Failed; + mCurrentBuffer = CanvasBuffer(); + return mCurrentBuffer; + } + + mCurrentBuffer = CanvasBuffer(std::move(newBuffer->shmem)); + return mCurrentBuffer; +} + +void CanvasDrawEventRecorder::DropFreeBuffers() { + while (mRecycledBuffers.size() > 1 && + mRecycledBuffers.front().eventCount < mHeader->processedCount) { + // If we encountered an error, we may have invalidated mCurrentBuffer in + // GetContiguousBuffer. No need to write the DROP_BUFFER event. + if (mCurrentBuffer.IsValid()) { + WriteInternalEvent(DROP_BUFFER); + } + mCurrentBuffer = CanvasBuffer(std::move(mRecycledBuffers.front().shmem)); + mRecycledBuffers.pop(); + } + + ClearProcessedExternalSurfaces(); +} + +void CanvasDrawEventRecorder::IncrementEventCount() { + mHeader->eventCount++; + CheckAndSignalReader(); +} + +void CanvasDrawEventRecorder::CheckAndSignalReader() { + do { + switch (mHeader->readerState) { + 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()) { + return; + } + continue; + case State::Waiting: + if (mHeader->processedCount < mHeader->eventCount) { + // We have to use compareExchange here because the reader can change + // from Waiting to Stopped. + if (mHeader->readerState.compareExchange(State::Waiting, + State::Processing)) { + mReaderSemaphore->Signal(); + return; + } + + MOZ_ASSERT(mHeader->readerState == State::Stopped); + continue; + } + return; + case State::Stopped: + if (mHeader->processedCount < mHeader->eventCount) { + mHeader->readerState = State::Processing; + if (!mHelpers->RestartReader()) { + mHeader->writerState = State::Failed; + } + } + return; + default: + MOZ_ASSERT_UNREACHABLE("Invalid waiting state."); + return; + } + } while (true); +} + +void CanvasDrawEventRecorder::StoreSourceSurfaceRecording( + gfx::SourceSurface* aSurface, const char* aReason) { + NS_ASSERT_OWNINGTHREAD(CanvasDrawEventRecorder); + + if (NS_IsMainThread()) { + wr::ExternalImageId extId{}; + nsresult rv = layers::SharedSurfacesChild::Share(aSurface, extId); + if (NS_SUCCEEDED(rv)) { + StoreExternalSurfaceRecording(aSurface, wr::AsUint64(extId)); + mExternalSurfaces.back().mEventCount = mHeader->eventCount; + return; + } + } + + DrawEventRecorderPrivate::StoreSourceSurfaceRecording(aSurface, aReason); +} + +void CanvasDrawEventRecorder::ClearProcessedExternalSurfaces() { + while (!mExternalSurfaces.empty()) { + if (mExternalSurfaces.front().mEventCount > mHeader->processedCount) { + break; + } + mExternalSurfaces.pop_front(); + } +} + +} // namespace layers +} // namespace mozilla |