Bug 1464032 Part 8: Add a CanvasEventRingBuffer and CanvasDrawEventRecorder. r=Gankro
authorBob Owen <bobowencode@gmail.com>
Sun, 02 Dec 2018 14:14:43 +0000
changeset 477759 fcc9f5f6dfe1c94e7f76582ee972e960539cb060
parent 477758 a2720ec3086f5c17bee8ddb394cd43ea0674c2f4
child 477760 0e6cf27e972802cd890667321ea8e37a043d8d38
push id113373
push userbobowencode@gmail.com
push dateFri, 07 Jun 2019 11:10:59 +0000
treeherdermozilla-inbound@2195b79ea888 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersGankro
bugs1464032
milestone69.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1464032 Part 8: Add a CanvasEventRingBuffer and CanvasDrawEventRecorder. r=Gankro These are to be used as part of recording canvas drawing in the content processes and playing it back in the GPU process through shared memory.
gfx/2d/RecordedEvent.cpp
gfx/2d/RecordedEvent.h
gfx/2d/RecordedEventImpl.h
gfx/layers/CanvasDrawEventRecorder.cpp
gfx/layers/CanvasDrawEventRecorder.h
--- a/gfx/2d/RecordedEvent.cpp
+++ b/gfx/2d/RecordedEvent.cpp
@@ -21,16 +21,23 @@ using namespace std;
 
 /* static */
 bool RecordedEvent::DoWithEventFromStream(
     EventStream& aStream, EventType aType,
     const std::function<bool(RecordedEvent*)>& aAction) {
   return DoWithEvent(aStream, aType, aAction);
 }
 
+/* static */
+bool RecordedEvent::DoWithEventFromStream(
+    EventRingBuffer& aStream, EventType aType,
+    const std::function<bool(RecordedEvent*)>& aAction) {
+  return DoWithEvent(aStream, aType, aAction);
+}
+
 string RecordedEvent::GetEventName(EventType aType) {
   switch (aType) {
     case DRAWTARGETCREATION:
       return "DrawTarget Creation";
     case DRAWTARGETDESTRUCTION:
       return "DrawTarget Destruction";
     case FILLRECT:
       return "FillRect";
--- a/gfx/2d/RecordedEvent.h
+++ b/gfx/2d/RecordedEvent.h
@@ -164,16 +164,81 @@ struct MemWriter {
   explicit MemWriter(char* aPtr) : mPtr(aPtr) {}
   void write(const char* aData, size_t aSize) {
     memcpy(mPtr, aData, aSize);
     mPtr += aSize;
   }
   char* mPtr;
 };
 
+// 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 when we have enough contiguous
+   * space we can record into the buffer quickly using MemWriter.
+   *
+   * @param aRecordedEvent the event to record
+   */
+  template <class RE>
+  void RecordEvent(const RE* aRecordedEvent) {
+    SizeCollector size;
+    WriteElement(size, aRecordedEvent->GetType());
+    aRecordedEvent->Record(size);
+    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);
+    }
+  }
+
+  /**
+   * 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;
+
+ protected:
+  /**
+   * Wait until space is available for writing and then set mBufPos and
+   * mAvailable.
+   */
+  virtual bool WaitForAndRecalculateAvailableSpace() = 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 {
   char* mData;
   size_t mLength;
   size_t mCapacity;
   void Resize(size_t aSize) {
     mLength = aSize;
     if (mLength > mCapacity) {
       mCapacity = mCapacity * 2;
@@ -269,16 +334,17 @@ class RecordedEvent {
    *                    objects and making playback decisions.
    * @return true unless a fatal problem has occurred and playback should
    * abort.
    */
   virtual bool PlayEvent(Translator* aTranslator) const { return true; }
 
   virtual void RecordToStream(std::ostream& aStream) const = 0;
   virtual void RecordToStream(EventStream& aStream) const = 0;
+  virtual void RecordToStream(EventRingBuffer& aStream) const = 0;
   virtual void RecordToStream(MemStream& aStream) const = 0;
 
   virtual void OutputSimpleEventInfo(std::stringstream& aStringStream) const {}
 
   template <class S>
   void RecordPatternData(S& aStream,
                          const PatternStorage& aPatternStorage) const;
   template <class S>
@@ -298,16 +364,19 @@ class RecordedEvent {
                                std::stringstream& aOutput) const;
 
   template <class S>
   static bool DoWithEvent(S& aStream, EventType aType,
                           const std::function<bool(RecordedEvent*)>& aAction);
   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; }
 
  protected:
   friend class DrawEventRecorderPrivate;
   friend class DrawEventRecorderFile;
   friend class DrawEventRecorderMemory;
   static void RecordUnscaledFont(UnscaledFont* aUnscaledFont,
--- a/gfx/2d/RecordedEventImpl.h
+++ b/gfx/2d/RecordedEventImpl.h
@@ -28,16 +28,19 @@ class RecordedEventDerived : public Reco
   void RecordToStream(std::ostream& aStream) const override {
     WriteElement(aStream, this->mType);
     static_cast<const Derived*>(this)->Record(aStream);
   }
   void RecordToStream(EventStream& aStream) const override {
     WriteElement(aStream, this->mType);
     static_cast<const Derived*>(this)->Record(aStream);
   }
+  void RecordToStream(EventRingBuffer& aStream) const final {
+    aStream.RecordEvent(static_cast<const Derived*>(this));
+  }
   void RecordToStream(MemStream& aStream) const override {
     SizeCollector size;
     WriteElement(size, this->mType);
     static_cast<const Derived*>(this)->Record(size);
 
     aStream.Resize(aStream.mLength + size.mTotalSize);
 
     MemWriter writer(aStream.mData + aStream.mLength - size.mTotalSize);
new file mode 100644
--- /dev/null
+++ b/gfx/layers/CanvasDrawEventRecorder.cpp
@@ -0,0 +1,455 @@
+/* -*- 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>
+
+namespace mozilla {
+namespace layers {
+
+static const int32_t kCheckpointEventType = -1;
+static const uint32_t kMaxSpinCount = 200;
+
+// TODO: These timeouts are long because failure in the middle of writing or
+// reading an event is probably going to be fatal to the GPU process. What we
+// really need to know is whether the other process has died.
+static const TimeDuration kWriterTimeout = TimeDuration::FromMilliseconds(1000);
+static const TimeDuration kReaderTimeout = TimeDuration::FromMilliseconds(5000);
+
+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,
+    const std::function<void()>& aResumeReaderCallback) {
+  mSharedMemory = MakeAndAddRef<ipc::SharedMemoryBasic>();
+  if (NS_WARN_IF(!mSharedMemory->Create(kShmemSize)) ||
+      NS_WARN_IF(!mSharedMemory->Map(kShmemSize))) {
+    return false;
+  }
+
+  if (NS_WARN_IF(!mSharedMemory->ShareToProcess(aOtherPid, 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->ShareToProcess(aOtherPid);
+  mReaderSemaphore->CloseHandle();
+  mWriterSemaphore.reset(
+      CrossProcessSemaphore::Create("SharedMemoryStreamChild", 0));
+  *aWriterSem = mWriterSemaphore->ShareToProcess(aOtherPid);
+  mWriterSemaphore->CloseHandle();
+
+  mResumeReaderCallback = aResumeReaderCallback;
+
+  mGood = true;
+  return true;
+}
+
+bool CanvasEventRingBuffer::InitReader(
+    const ipc::SharedMemoryBasic::Handle& aReadHandle,
+    const CrossProcessSemaphoreHandle& aReaderSem,
+    const CrossProcessSemaphoreHandle& aWriterSem) {
+  mSharedMemory = MakeAndAddRef<ipc::SharedMemoryBasic>();
+  if (NS_WARN_IF(!mSharedMemory->SetHandle(
+          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(aReaderSem));
+  mReaderSemaphore->CloseHandle();
+  mWriterSemaphore.reset(CrossProcessSemaphore::Create(aWriterSem));
+  mWriterSemaphore->CloseHandle();
+  mGood = true;
+  return true;
+}
+
+bool CanvasEventRingBuffer::WaitForAndRecalculateAvailableSpace() {
+  uint32_t bufPos = mOurCount % kStreamSize;
+  uint32_t maxToWrite = kStreamSize - bufPos;
+  mAvailable = std::min(maxToWrite, WaitForBytesToWrite());
+  if (!mAvailable) {
+    mGood = false;
+    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() {
+  uint32_t bufPos = mOurCount % kStreamSize;
+  uint32_t maxToRead = kStreamSize - bufPos;
+  mAvailable = std::min(maxToRead, WaitForBytesToRead());
+  if (!mAvailable) {
+    mGood = false;
+    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:
+        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.
+        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;
+          mResumeReaderCallback();
+        }
+        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) {
+  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;
+  if (!mReaderSemaphore->Wait(Some(aTimeout))) {
+    // We have to use compareExchange here because the writer can change our
+    // state if we are waiting.
+    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;
+  }
+
+  MOZ_RELEASE_ASSERT(HasDataToRead());
+
+  return true;
+}
+
+int32_t CanvasEventRingBuffer::ReadNextEvent() {
+  int32_t nextEvent;
+  ReadElement(*this, nextEvent);
+  while (nextEvent == kCheckpointEventType) {
+    ReadElement(*this, nextEvent);
+  }
+
+  return nextEvent;
+}
+
+uint32_t CanvasEventRingBuffer::CreateCheckpoint() {
+  WriteElement(*this, kCheckpointEventType);
+  return mOurCount;
+}
+
+bool CanvasEventRingBuffer::WaitForCheckpoint(uint32_t aCheckpoint,
+                                              TimeDuration aTimeout) {
+  return WaitForReadCount(aCheckpoint, aTimeout);
+}
+
+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.
+        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;
+
+  uint32_t lastReadCount = mRead->count;
+  while (!mWriterSemaphore->Wait(Some(aTimeout))) {
+    if (NS_WARN_IF(mRead->count == lastReadCount)) {
+      return false;
+    }
+    lastReadCount = mRead->count;
+  }
+
+  MOZ_ASSERT(mOurCount - mRead->count <= requiredDifference);
+  return true;
+}
+
+uint32_t CanvasEventRingBuffer::WaitForBytesToWrite() {
+  uint32_t streamFullReadCount = mOurCount - kStreamSize;
+  if (!WaitForReadCount(streamFullReadCount + 1, kWriterTimeout)) {
+    mGood = false;
+    return 0;
+  }
+
+  return mRead->count - streamFullReadCount;
+}
+
+uint32_t CanvasEventRingBuffer::WaitForBytesToRead() {
+  if (!WaitForDataToRead(kReaderTimeout)) {
+    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;
+    }
+
+    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) {
+  uint32_t readCount = mWrite->returnCount;
+  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 {
+      // Double-check that the reader isn't waiting.
+      CheckAndSignalReader();
+    }
+
+    availableToRead = std::min(bufRemaining, (mRead->returnCount - readCount));
+  }
+
+  memcpy(aOut, mBuf + bufPos, aSize);
+  readCount += aSize;
+  mWrite->returnCount = readCount;
+}
+
+}  // namespace layers
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/layers/CanvasDrawEventRecorder.h
@@ -0,0 +1,232 @@
+/* -*- 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/. */
+
+#ifndef mozilla_layers_CanvasDrawEventRecorder_h
+#define mozilla_layers_CanvasDrawEventRecorder_h
+
+#include "mozilla/gfx/DrawEventRecorder.h"
+#include "mozilla/ipc/CrossProcessSemaphore.h"
+#include "mozilla/ipc/SharedMemoryBasic.h"
+
+namespace mozilla {
+namespace layers {
+
+class CanvasEventRingBuffer final : public gfx::EventRingBuffer {
+ public:
+  CanvasEventRingBuffer() {}
+
+  /**
+   * 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 aResumeReaderCallback callback to start the reader when it is has
+   *                             stopped
+   * @returns true if initialization succeeds
+   */
+  bool InitWriter(base::ProcessId aOtherPid,
+                  ipc::SharedMemoryBasic::Handle* aReadHandle,
+                  CrossProcessSemaphoreHandle* aReaderSem,
+                  CrossProcessSemaphoreHandle* aWriterSem,
+                  const std::function<void()>& aResumeReaderCallback);
+
+  /**
+   * 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
+   * @returns true if initialization succeeds
+   */
+  bool InitReader(const ipc::SharedMemoryBasic::Handle& aReadHandle,
+                  const CrossProcessSemaphoreHandle& aReaderSem,
+                  const CrossProcessSemaphoreHandle& aWriterSem);
+
+  bool good() const final { return mGood; }
+
+  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 the given TimeDuration for data to become available.
+   *
+   * @returns true if data is available to read.
+   */
+  bool WaitForDataToRead(TimeDuration aTimeout);
+
+  int32_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 read count has
+   *          not changed in the aTimeout duration.
+   */
+  bool WaitForCheckpoint(uint32_t aCheckpoint, TimeDuration aTimeout);
+
+  /**
+   * 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);
+
+ protected:
+  bool WaitForAndRecalculateAvailableSpace() final;
+  void UpdateWriteTotalsBy(uint32_t aCount) final;
+
+ private:
+  enum class State : uint32_t {
+    Processing,
+
+    /**
+     * This is the important state to make sure the other side signals or starts
+     * us as soon as data or space is available. We set AboutToWait first and
+     * then re-check the condition. If we went straight to Waiting or Stopped
+     * then in between the last check and setting the state, the other side
+     * could have used all available data or space and never have signaled us
+     * because it didn't know we were about to wait, causing a deadlock.
+     * While we are in this state, the other side must wait until we resolve the
+     * AboutToWait state to one of the other states and then signal or start us
+     * if it needs to.
+     */
+    AboutToWait,
+    Waiting,
+    Stopped
+  };
+
+  struct ReadFooter {
+    Atomic<uint32_t, ReleaseAcquire> count;
+    Atomic<uint32_t, ReleaseAcquire> returnCount;
+    Atomic<State, ReleaseAcquire> state;
+  };
+
+  struct WriteFooter {
+    Atomic<uint32_t, ReleaseAcquire> count;
+    Atomic<uint32_t, ReleaseAcquire> returnCount;
+    Atomic<uint32_t, ReleaseAcquire> requiredDifference;
+    Atomic<State, ReleaseAcquire> state;
+  };
+
+  CanvasEventRingBuffer(const CanvasEventRingBuffer&) = delete;
+  void operator=(const CanvasEventRingBuffer&) = delete;
+
+  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();
+
+  RefPtr<ipc::SharedMemoryBasic> mSharedMemory;
+  UniquePtr<CrossProcessSemaphore> mReaderSemaphore;
+  UniquePtr<CrossProcessSemaphore> mWriterSemaphore;
+  std::function<void()> mResumeReaderCallback;
+  char* mBuf = nullptr;
+  uint32_t mOurCount = 0;
+  WriteFooter* mWrite = nullptr;
+  ReadFooter* mRead = nullptr;
+  bool mGood = false;
+};
+
+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,
+            const std::function<void()>& aResumeReaderCallback) {
+    return mOutputStream.InitWriter(aOtherPid, aHandle, aReaderSem, aWriterSem,
+                                    aResumeReaderCallback);
+  }
+
+  void RecordEvent(const gfx::RecordedEvent& aEvent) final {
+    if (!mOutputStream.good()) {
+      return;
+    }
+
+    aEvent.RecordToStream(mOutputStream);
+  }
+
+  void Flush() final {}
+
+  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.
+   *
+   * @params aCheckpoint the checkpoint to wait for
+   * @params aTimeout duration to wait while translator is not actively reading
+   * @returns true if the checkpoint was reached, false if the translator has
+   *          not been active in the aTimeout duration.
+   */
+  bool WaitForCheckpoint(uint32_t aCheckpoint, TimeDuration aTimeout) {
+    return mOutputStream.WaitForCheckpoint(aCheckpoint, aTimeout);
+  }
+
+ private:
+  CanvasEventRingBuffer mOutputStream;
+};
+
+}  // namespace layers
+}  // namespace mozilla
+
+#endif  // mozilla_layers_CanvasDrawEventRecorder_h