Bug 1764201 Part 3: Remove screen info stuff from gfxPlatform. r=jgilbert,geckoview...
[gecko.git] / gfx / layers / CanvasDrawEventRecorder.cpp
blobaf143bf5cbdbbfccf3a836605befc5d8db6aa208
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "CanvasDrawEventRecorder.h"
9 #include <string.h>
11 #include "mozilla/dom/WorkerCommon.h"
12 #include "mozilla/dom/WorkerPrivate.h"
13 #include "mozilla/dom/WorkerRef.h"
14 #include "mozilla/dom/WorkerRunnable.h"
15 #include "mozilla/layers/TextureRecorded.h"
16 #include "mozilla/layers/SharedSurfacesChild.h"
17 #include "mozilla/StaticPrefs_gfx.h"
18 #include "RecordedCanvasEventImpl.h"
20 namespace mozilla {
21 namespace layers {
23 struct ShmemAndHandle {
24 RefPtr<ipc::SharedMemoryBasic> shmem;
25 Handle handle;
28 static Maybe<ShmemAndHandle> CreateAndMapShmem(size_t aSize) {
29 auto shmem = MakeRefPtr<ipc::SharedMemoryBasic>();
30 if (!shmem->Create(aSize) || !shmem->Map(aSize)) {
31 return Nothing();
34 auto shmemHandle = shmem->TakeHandle();
35 if (!shmemHandle) {
36 return Nothing();
39 return Some(ShmemAndHandle{shmem.forget(), std::move(shmemHandle)});
42 CanvasDrawEventRecorder::CanvasDrawEventRecorder(
43 dom::ThreadSafeWorkerRef* aWorkerRef)
44 : mWorkerRef(aWorkerRef), mIsOnWorker(!!aWorkerRef) {
45 mDefaultBufferSize = ipc::SharedMemory::PageAlignedSize(
46 StaticPrefs::gfx_canvas_remote_default_buffer_size());
47 mMaxDefaultBuffers = StaticPrefs::gfx_canvas_remote_max_default_buffers();
48 mMaxSpinCount = StaticPrefs::gfx_canvas_remote_max_spin_count();
49 mDropBufferLimit = StaticPrefs::gfx_canvas_remote_drop_buffer_limit();
50 mDropBufferOnZero = mDropBufferLimit;
53 CanvasDrawEventRecorder::~CanvasDrawEventRecorder() { MOZ_ASSERT(!mWorkerRef); }
55 bool CanvasDrawEventRecorder::Init(TextureType aTextureType,
56 TextureType aWebglTextureType,
57 gfx::BackendType aBackendType,
58 UniquePtr<Helpers> aHelpers) {
59 NS_ASSERT_OWNINGTHREAD(CanvasDrawEventRecorder);
61 mHelpers = std::move(aHelpers);
63 MOZ_ASSERT(mTextureType == TextureType::Unknown);
64 auto header = CreateAndMapShmem(sizeof(Header));
65 if (NS_WARN_IF(header.isNothing())) {
66 return false;
69 mHeader = static_cast<Header*>(header->shmem->memory());
70 mHeader->eventCount = 0;
71 mHeader->writerWaitCount = 0;
72 mHeader->writerState = State::Processing;
73 mHeader->processedCount = 0;
74 mHeader->readerState = State::Paused;
76 // We always keep at least two buffers. This means that when we
77 // have to add a new buffer, there is at least a full buffer that requires
78 // translating while the handle is sent over.
79 AutoTArray<Handle, 2> bufferHandles;
80 auto buffer = CreateAndMapShmem(mDefaultBufferSize);
81 if (NS_WARN_IF(buffer.isNothing())) {
82 return false;
84 mCurrentBuffer = CanvasBuffer(std::move(buffer->shmem));
85 bufferHandles.AppendElement(std::move(buffer->handle));
87 buffer = CreateAndMapShmem(mDefaultBufferSize);
88 if (NS_WARN_IF(buffer.isNothing())) {
89 return false;
91 mRecycledBuffers.emplace(buffer->shmem.forget(), 0);
92 bufferHandles.AppendElement(std::move(buffer->handle));
94 mWriterSemaphore.reset(CrossProcessSemaphore::Create("CanvasRecorder", 0));
95 auto writerSem = mWriterSemaphore->CloneHandle();
96 mWriterSemaphore->CloseHandle();
97 if (!IsHandleValid(writerSem)) {
98 return false;
101 mReaderSemaphore.reset(CrossProcessSemaphore::Create("CanvasTranslator", 0));
102 auto readerSem = mReaderSemaphore->CloneHandle();
103 mReaderSemaphore->CloseHandle();
104 if (!IsHandleValid(readerSem)) {
105 return false;
108 if (!mHelpers->InitTranslator(aTextureType, aWebglTextureType, aBackendType,
109 std::move(header->handle),
110 std::move(bufferHandles), mDefaultBufferSize,
111 std::move(readerSem), std::move(writerSem))) {
112 return false;
115 mTextureType = aTextureType;
116 mHeaderShmem = header->shmem;
117 return true;
120 void CanvasDrawEventRecorder::RecordEvent(const gfx::RecordedEvent& aEvent) {
121 NS_ASSERT_OWNINGTHREAD(CanvasDrawEventRecorder);
122 aEvent.RecordToStream(*this);
125 int64_t CanvasDrawEventRecorder::CreateCheckpoint() {
126 NS_ASSERT_OWNINGTHREAD(CanvasDrawEventRecorder);
127 int64_t checkpoint = mHeader->eventCount;
128 RecordEvent(RecordedCheckpoint());
129 ClearProcessedExternalSurfaces();
130 return checkpoint;
133 bool CanvasDrawEventRecorder::WaitForCheckpoint(int64_t aCheckpoint) {
134 NS_ASSERT_OWNINGTHREAD(CanvasDrawEventRecorder);
136 uint32_t spinCount = mMaxSpinCount;
137 do {
138 if (mHeader->processedCount >= aCheckpoint) {
139 return true;
141 } while (--spinCount != 0);
143 mHeader->writerState = State::AboutToWait;
144 if (mHeader->processedCount >= aCheckpoint) {
145 mHeader->writerState = State::Processing;
146 return true;
149 mHeader->writerWaitCount = aCheckpoint;
150 mHeader->writerState = State::Waiting;
152 // Wait unless we detect the reading side has closed.
153 while (!mHelpers->ReaderClosed() && mHeader->readerState != State::Failed) {
154 if (mWriterSemaphore->Wait(Some(TimeDuration::FromMilliseconds(100)))) {
155 MOZ_ASSERT(mHeader->processedCount >= aCheckpoint);
156 return true;
160 // Either the reader has failed or we're stopping writing for some other
161 // reason (e.g. shutdown), so mark us as failed so the reader is aware.
162 mHeader->writerState = State::Failed;
163 return false;
166 void CanvasDrawEventRecorder::WriteInternalEvent(EventType aEventType) {
167 MOZ_ASSERT(mCurrentBuffer.SizeRemaining() > 0);
169 WriteElement(mCurrentBuffer.Writer(), aEventType);
170 IncrementEventCount();
173 gfx::ContiguousBuffer& CanvasDrawEventRecorder::GetContiguousBuffer(
174 size_t aSize) {
175 if (!mCurrentBuffer.IsValid()) {
176 // If the current buffer is invalid then we've already failed previously.
177 MOZ_ASSERT(mHeader->writerState == State::Failed);
178 return mCurrentBuffer;
181 // We make sure that our buffer can hold aSize + 1 to ensure we always have
182 // room for the end of buffer event.
184 // Check if there is enough room is our current buffer.
185 if (mCurrentBuffer.SizeRemaining() > aSize) {
186 return mCurrentBuffer;
189 bool useRecycledBuffer = false;
190 if (mRecycledBuffers.front().Capacity() > aSize) {
191 // The recycled buffer is big enough, check if it is free.
192 if (mRecycledBuffers.front().eventCount <= mHeader->processedCount) {
193 useRecycledBuffer = true;
194 } else if (mRecycledBuffers.size() >= mMaxDefaultBuffers) {
195 // We've hit he max number of buffers, wait for the next one to be free.
196 // We wait for (eventCount - 1), as we check and signal in the translator
197 // during the play event, before the processedCount has been updated.
198 useRecycledBuffer = true;
199 if (!WaitForCheckpoint(mRecycledBuffers.front().eventCount - 1)) {
200 // The wait failed or we're shutting down, just return an empty buffer.
201 mCurrentBuffer = CanvasBuffer();
202 return mCurrentBuffer;
207 if (useRecycledBuffer) {
208 // Only queue default size buffers for recycling.
209 if (mCurrentBuffer.Capacity() == mDefaultBufferSize) {
210 WriteInternalEvent(RECYCLE_BUFFER);
211 mRecycledBuffers.emplace(std::move(mCurrentBuffer.shmem),
212 mHeader->eventCount);
213 } else {
214 WriteInternalEvent(DROP_BUFFER);
217 mCurrentBuffer = CanvasBuffer(std::move(mRecycledBuffers.front().shmem));
218 mRecycledBuffers.pop();
220 // If we have more than one recycled buffers free a configured number of
221 // times in a row then drop one.
222 if (mRecycledBuffers.size() > 1 &&
223 mRecycledBuffers.front().eventCount < mHeader->processedCount) {
224 if (--mDropBufferOnZero == 0) {
225 WriteInternalEvent(DROP_BUFFER);
226 mCurrentBuffer =
227 CanvasBuffer(std::move(mRecycledBuffers.front().shmem));
228 mRecycledBuffers.pop();
229 mDropBufferOnZero = 1;
231 } else {
232 mDropBufferOnZero = mDropBufferLimit;
235 return mCurrentBuffer;
238 // We don't have a buffer free or it is not big enough, so create a new one.
239 WriteInternalEvent(PAUSE_TRANSLATION);
241 // Only queue default size buffers for recycling.
242 if (mCurrentBuffer.Capacity() == mDefaultBufferSize) {
243 mRecycledBuffers.emplace(std::move(mCurrentBuffer.shmem),
244 mHeader->eventCount);
247 size_t bufferSize = std::max(mDefaultBufferSize,
248 ipc::SharedMemory::PageAlignedSize(aSize + 1));
249 auto newBuffer = CreateAndMapShmem(bufferSize);
250 if (NS_WARN_IF(newBuffer.isNothing())) {
251 mHeader->writerState = State::Failed;
252 mCurrentBuffer = CanvasBuffer();
253 return mCurrentBuffer;
256 if (!mHelpers->AddBuffer(std::move(newBuffer->handle), bufferSize)) {
257 mHeader->writerState = State::Failed;
258 mCurrentBuffer = CanvasBuffer();
259 return mCurrentBuffer;
262 mCurrentBuffer = CanvasBuffer(std::move(newBuffer->shmem));
263 return mCurrentBuffer;
266 void CanvasDrawEventRecorder::DropFreeBuffers() {
267 while (mRecycledBuffers.size() > 1 &&
268 mRecycledBuffers.front().eventCount < mHeader->processedCount) {
269 // If we encountered an error, we may have invalidated mCurrentBuffer in
270 // GetContiguousBuffer. No need to write the DROP_BUFFER event.
271 if (mCurrentBuffer.IsValid()) {
272 WriteInternalEvent(DROP_BUFFER);
274 mCurrentBuffer = CanvasBuffer(std::move(mRecycledBuffers.front().shmem));
275 mRecycledBuffers.pop();
278 ClearProcessedExternalSurfaces();
281 void CanvasDrawEventRecorder::IncrementEventCount() {
282 mHeader->eventCount++;
283 CheckAndSignalReader();
286 void CanvasDrawEventRecorder::CheckAndSignalReader() {
287 do {
288 switch (mHeader->readerState) {
289 case State::Processing:
290 case State::Paused:
291 case State::Failed:
292 return;
293 case State::AboutToWait:
294 // The reader is making a decision about whether to wait. So, we must
295 // wait until it has decided to avoid races. Check if the reader is
296 // closed to avoid hangs.
297 if (mHelpers->ReaderClosed()) {
298 return;
300 continue;
301 case State::Waiting:
302 if (mHeader->processedCount < mHeader->eventCount) {
303 // We have to use compareExchange here because the reader can change
304 // from Waiting to Stopped.
305 if (mHeader->readerState.compareExchange(State::Waiting,
306 State::Processing)) {
307 mReaderSemaphore->Signal();
308 return;
311 MOZ_ASSERT(mHeader->readerState == State::Stopped);
312 continue;
314 return;
315 case State::Stopped:
316 if (mHeader->processedCount < mHeader->eventCount) {
317 mHeader->readerState = State::Processing;
318 if (!mHelpers->RestartReader()) {
319 mHeader->writerState = State::Failed;
322 return;
323 default:
324 MOZ_ASSERT_UNREACHABLE("Invalid waiting state.");
325 return;
327 } while (true);
330 void CanvasDrawEventRecorder::DetachResources() {
331 NS_ASSERT_OWNINGTHREAD(CanvasDrawEventRecorder);
333 DrawEventRecorderPrivate::DetachResources();
336 auto lockedPendingDeletions = mPendingDeletions.Lock();
337 mWorkerRef = nullptr;
341 void CanvasDrawEventRecorder::QueueProcessPendingDeletionsLocked(
342 RefPtr<CanvasDrawEventRecorder>&& aRecorder) {
343 if (!mWorkerRef) {
344 MOZ_RELEASE_ASSERT(
345 !mIsOnWorker,
346 "QueueProcessPendingDeletionsLocked called after worker shutdown!");
348 NS_DispatchToMainThread(NS_NewRunnableFunction(
349 "CanvasDrawEventRecorder::QueueProcessPendingDeletionsLocked",
350 [self = std::move(aRecorder)]() { self->ProcessPendingDeletions(); }));
351 return;
354 if (!NS_IsMainThread()) {
355 NS_DispatchToMainThread(NS_NewRunnableFunction(
356 "CanvasDrawEventRecorder::QueueProcessPendingDeletionsLocked",
357 [self = std::move(aRecorder)]() mutable {
358 self->QueueProcessPendingDeletions(std::move(self));
359 }));
360 return;
363 class ProcessPendingRunnable final : public dom::WorkerRunnable {
364 public:
365 ProcessPendingRunnable(dom::WorkerPrivate* aWorkerPrivate,
366 RefPtr<CanvasDrawEventRecorder>&& aRecorder)
367 : dom::WorkerRunnable(aWorkerPrivate),
368 mRecorder(std::move(aRecorder)) {}
370 bool WorkerRun(JSContext*, dom::WorkerPrivate*) override {
371 RefPtr<CanvasDrawEventRecorder> recorder = std::move(mRecorder);
372 recorder->ProcessPendingDeletions();
373 return true;
376 private:
377 RefPtr<CanvasDrawEventRecorder> mRecorder;
380 auto task = MakeRefPtr<ProcessPendingRunnable>(mWorkerRef->Private(),
381 std::move(aRecorder));
382 if (NS_WARN_IF(!task->Dispatch())) {
383 MOZ_CRASH("ProcessPendingRunnable leaked!");
387 void CanvasDrawEventRecorder::QueueProcessPendingDeletions(
388 RefPtr<CanvasDrawEventRecorder>&& aRecorder) {
389 auto lockedPendingDeletions = mPendingDeletions.Lock();
390 if (lockedPendingDeletions->empty()) {
391 // We raced to handle the deletions, and something got there first.
392 return;
395 QueueProcessPendingDeletionsLocked(std::move(aRecorder));
398 void CanvasDrawEventRecorder::AddPendingDeletion(
399 std::function<void()>&& aPendingDeletion) {
400 PendingDeletionsVector pendingDeletions;
403 auto lockedPendingDeletions = mPendingDeletions.Lock();
404 bool wasEmpty = lockedPendingDeletions->empty();
405 lockedPendingDeletions->emplace_back(std::move(aPendingDeletion));
407 MOZ_RELEASE_ASSERT(!mIsOnWorker || mWorkerRef,
408 "AddPendingDeletion called after worker shutdown!");
410 // If we are not on the owning thread, we must queue an event to run the
411 // deletions, if we transitioned from empty to non-empty.
412 if ((mWorkerRef && !mWorkerRef->Private()->IsOnCurrentThread()) ||
413 (!mWorkerRef && !NS_IsMainThread())) {
414 if (wasEmpty) {
415 RefPtr<CanvasDrawEventRecorder> self(this);
416 QueueProcessPendingDeletionsLocked(std::move(self));
418 return;
421 // Otherwise, we can just run all of them right now.
422 pendingDeletions.swap(*lockedPendingDeletions);
425 for (const auto& pendingDeletion : pendingDeletions) {
426 pendingDeletion();
430 void CanvasDrawEventRecorder::StoreSourceSurfaceRecording(
431 gfx::SourceSurface* aSurface, const char* aReason) {
432 NS_ASSERT_OWNINGTHREAD(CanvasDrawEventRecorder);
434 if (NS_IsMainThread()) {
435 wr::ExternalImageId extId{};
436 nsresult rv = layers::SharedSurfacesChild::Share(aSurface, extId);
437 if (NS_SUCCEEDED(rv)) {
438 StoreExternalSurfaceRecording(aSurface, wr::AsUint64(extId));
439 mExternalSurfaces.back().mEventCount = mHeader->eventCount;
440 return;
444 DrawEventRecorderPrivate::StoreSourceSurfaceRecording(aSurface, aReason);
447 void CanvasDrawEventRecorder::ClearProcessedExternalSurfaces() {
448 while (!mExternalSurfaces.empty()) {
449 if (mExternalSurfaces.front().mEventCount > mHeader->processedCount) {
450 break;
452 mExternalSurfaces.pop_front();
456 } // namespace layers
457 } // namespace mozilla