Bug 1551735 - Add a thread-safe composition recorder for WebRender r=kvark,kats
authorBarret Rennie <barret@brennie.ca>
Fri, 31 May 2019 00:31:24 +0000
changeset 476296 ced6621fcb1b3e02bb6d5ac4903887760a671c6f
parent 476295 58dd508e43e6932b4606865282967f57927b757c
child 476297 0ef19da1d641b06c3238892d7e81ebc27c85b98d
push id36090
push usernbeleuzu@mozilla.com
push dateFri, 31 May 2019 03:59:09 +0000
treeherdermozilla-central@63568b2a8178 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskvark, kats
bugs1551735
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 1551735 - Add a thread-safe composition recorder for WebRender r=kvark,kats Since WebRender does its rendering on a separate thread from the compositor thread, we need a composition recorder that can be shared between threads. Differential Revision: https://phabricator.services.mozilla.com/D32231
gfx/layers/CompositionRecorder.cpp
gfx/layers/CompositionRecorder.h
gfx/layers/moz.build
gfx/layers/wr/WebRenderCompositionRecorder.cpp
gfx/layers/wr/WebRenderCompositionRecorder.h
gfx/webrender_bindings/src/bindings.rs
--- a/gfx/layers/CompositionRecorder.cpp
+++ b/gfx/layers/CompositionRecorder.cpp
@@ -22,18 +22,16 @@
 using namespace mozilla::gfx;
 
 namespace mozilla {
 namespace layers {
 
 CompositionRecorder::CompositionRecorder(TimeStamp aRecordingStart)
     : mRecordingStart(aRecordingStart) {}
 
-CompositionRecorder::~CompositionRecorder() {}
-
 void CompositionRecorder::RecordFrame(RecordedFrame* aFrame) {
   mCollectedFrames.AppendElement(aFrame);
 }
 
 void CompositionRecorder::WriteCollectedFrames() {
   std::time_t t = std::time(nullptr);
   std::tm tm = *std::localtime(&t);
   std::stringstream str;
--- a/gfx/layers/CompositionRecorder.h
+++ b/gfx/layers/CompositionRecorder.h
@@ -44,34 +44,34 @@ class RecordedFrame {
  * A recorder for composited frames.
  *
  * This object collects frames sent to it by a |LayerManager| and writes them
  * out as a series of images until recording has finished.
  *
  * If GPU-accelerated rendering is used, the frames will not be mapped into
  * memory until |WriteCollectedFrames| is called.
  */
-class CompositionRecorder final {
-  NS_INLINE_DECL_REFCOUNTING(CompositionRecorder)
+class CompositionRecorder {
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CompositionRecorder)
 
  public:
   explicit CompositionRecorder(TimeStamp aRecordingStart);
 
   /**
    * Record a composited frame.
    */
-  void RecordFrame(RecordedFrame* aFrame);
+  virtual void RecordFrame(RecordedFrame* aFrame);
 
   /**
    * Write out the collected frames as a series of timestamped images.
    */
-  void WriteCollectedFrames();
+  virtual void WriteCollectedFrames();
 
  protected:
-  ~CompositionRecorder();
+  virtual ~CompositionRecorder() = default;
 
  private:
   nsTArray<RefPtr<RecordedFrame>> mCollectedFrames;
   TimeStamp mRecordingStart;
 };
 
 }  // namespace layers
 }  // namespace mozilla
--- a/gfx/layers/moz.build
+++ b/gfx/layers/moz.build
@@ -250,16 +250,17 @@ EXPORTS.mozilla.layers += [
     'wr/RenderRootBoundary.h',
     'wr/RenderRootStateManager.h',
     'wr/RenderRootTypes.h',
     'wr/StackingContextHelper.h',
     'wr/WebRenderBridgeChild.h',
     'wr/WebRenderBridgeParent.h',
     'wr/WebRenderCanvasRenderer.h',
     'wr/WebRenderCommandBuilder.h',
+    'wr/WebRenderCompositionRecorder.h',
     'wr/WebRenderDrawEventRecorder.h',
     'wr/WebRenderImageHost.h',
     'wr/WebRenderLayerManager.h',
     'wr/WebRenderLayersLogging.h',
     'wr/WebRenderMessageUtils.h',
     'wr/WebRenderScrollData.h',
     'wr/WebRenderScrollDataWrapper.h',
     'wr/WebRenderTextureHost.h',
@@ -498,16 +499,17 @@ UNIFIED_SOURCES += [
     'wr/IpcResourceUpdateQueue.cpp',
     'wr/RenderRootStateManager.cpp',
     'wr/RenderRootTypes.cpp',
     'wr/StackingContextHelper.cpp',
     'wr/WebRenderBridgeChild.cpp',
     'wr/WebRenderBridgeParent.cpp',
     'wr/WebRenderCanvasRenderer.cpp',
     'wr/WebRenderCommandBuilder.cpp',
+    'wr/WebRenderCompositionRecorder.cpp',
     'wr/WebRenderDrawEventRecorder.cpp',
     'wr/WebRenderImageHost.cpp',
     'wr/WebRenderLayerManager.cpp',
     'wr/WebRenderLayersLogging.cpp',
     'wr/WebRenderScrollData.cpp',
     'wr/WebRenderTextureHostWrapper.cpp',
     'wr/WebRenderUserData.cpp',
     # XXX here are some unified build error.
new file mode 100644
--- /dev/null
+++ b/gfx/layers/wr/WebRenderCompositionRecorder.cpp
@@ -0,0 +1,144 @@
+/* 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 "WebRenderCompositionRecorder.h"
+
+#include "mozilla/webrender/RenderThread.h"
+
+namespace mozilla {
+
+namespace layers {
+
+class RendererRecordedFrame final : public layers::RecordedFrame {
+ public:
+  RendererRecordedFrame(const TimeStamp& aTimeStamp, wr::Renderer* aRenderer,
+                        const wr::RecordedFrameHandle aHandle,
+                        const gfx::IntSize& aSize)
+      : RecordedFrame(aTimeStamp),
+        mRenderer(aRenderer),
+        mSize(aSize),
+        mHandle(aHandle) {}
+
+  already_AddRefed<gfx::DataSourceSurface> GetSourceSurface() override {
+    if (!mSurface) {
+      mSurface = gfx::Factory::CreateDataSourceSurface(
+          mSize, gfx::SurfaceFormat::B8G8R8A8, /* aZero = */ false);
+
+      gfx::DataSourceSurface::ScopedMap map(mSurface,
+                                            gfx::DataSourceSurface::WRITE);
+
+      if (!wr_renderer_map_recorded_frame(mRenderer, mHandle, map.GetData(),
+                                          mSize.width * mSize.height * 4,
+                                          mSize.width * 4)) {
+        return nullptr;
+      }
+    }
+
+    return do_AddRef(mSurface);
+  }
+
+ private:
+  wr::Renderer* mRenderer;
+  RefPtr<gfx::DataSourceSurface> mSurface;
+  gfx::IntSize mSize;
+  wr::RecordedFrameHandle mHandle;
+};
+
+void WebRenderCompositionRecorder::RecordFrame(RecordedFrame* aFrame) {
+  MOZ_CRASH(
+      "WebRenderCompositionRecorder::RecordFrame should not be called; call "
+      "MaybeRecordFrame instead.");
+}
+
+bool WebRenderCompositionRecorder::MaybeRecordFrame(
+    wr::Renderer* aRenderer, wr::WebRenderPipelineInfo* aFrameEpochs) {
+  MOZ_ASSERT(wr::RenderThread::IsInRenderThread());
+
+  if (!aRenderer || !aFrameEpochs) {
+    return false;
+  }
+
+  if (!mMutex.TryLock()) {
+    // If we cannot lock the mutex, then the |CompositorBridgeParent|
+    // is holding the mutex in |WriteCollectedFrames|.
+    //
+    // In either case we do not want to wait to acquire the mutex to record a
+    // frame since frames recorded now will not be written to disk.
+
+    return false;
+  }
+
+  auto unlockGuard = MakeScopeExit([&]() { mMutex.Unlock(); });
+
+  if (mFinishedRecording) {
+    return true;
+  }
+
+  if (!DidPaintContent(aFrameEpochs)) {
+    return false;
+  }
+
+  wr::RecordedFrameHandle handle{0};
+  gfx::IntSize size(0, 0);
+
+  if (wr_renderer_record_frame(aRenderer, wr::ImageFormat::BGRA8, &handle,
+                               &size.width, &size.height)) {
+    RefPtr<RecordedFrame> frame =
+        new RendererRecordedFrame(TimeStamp::Now(), aRenderer, handle, size);
+
+    CompositionRecorder::RecordFrame(frame);
+  }
+
+  return false;
+}
+
+void WebRenderCompositionRecorder::WriteCollectedFrames() {
+  MutexAutoLock guard(mMutex);
+
+  MOZ_RELEASE_ASSERT(
+      !mFinishedRecording,
+      "WebRenderCompositionRecorder: Attempting to write frames from invalid "
+      "state.");
+
+  CompositionRecorder::WriteCollectedFrames();
+
+  mFinishedRecording = true;
+}
+
+bool WebRenderCompositionRecorder::DidPaintContent(
+    wr::WebRenderPipelineInfo* aFrameEpochs) {
+  const wr::WrPipelineInfo& info = aFrameEpochs->Raw();
+  bool didPaintContent = false;
+
+  for (wr::usize i = 0; i < info.epochs.length; i++) {
+    const wr::PipelineId pipelineId = info.epochs.data[i].pipeline_id;
+
+    if (pipelineId == mRootPipelineId) {
+      continue;
+    }
+
+    const auto it = mContentPipelines.find(AsUint64(pipelineId));
+    if (it == mContentPipelines.end() ||
+        it->second != info.epochs.data[i].epoch) {
+      // This content pipeline has updated list last render or has newly
+      // rendered.
+      didPaintContent = true;
+      mContentPipelines[AsUint64(pipelineId)] = info.epochs.data[i].epoch;
+    }
+  }
+
+  for (wr::usize i = 0; i < info.removed_pipelines.length; i++) {
+    const wr::PipelineId pipelineId =
+        info.removed_pipelines.data[i].pipeline_id;
+    if (pipelineId == mRootPipelineId) {
+      continue;
+    }
+    mContentPipelines.erase(AsUint64(pipelineId));
+  }
+
+  return didPaintContent;
+}
+
+}  // namespace layers
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/layers/wr/WebRenderCompositionRecorder.h
@@ -0,0 +1,115 @@
+/* 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_WebRenderCompositionRecorder_h
+#define mozilla_layers_WebRenderCompositionRecorder_h
+
+#include "CompositionRecorder.h"
+
+#include "mozilla/Mutex.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/webrender/webrender_ffi.h"
+
+#include <unordered_map>
+
+namespace mozilla {
+
+namespace wr {
+class WebRenderPipelineInfo;
+}
+
+namespace layers {
+
+/**
+ * A thread-safe version of the |CompositionRecorder|.
+ *
+ * Composition recording for WebRender occurs on the |RenderThread| whereas the
+ * frames are written on the thread holding the |CompositorBridgeParent|.
+ *
+ */
+class WebRenderCompositionRecorder final : public CompositionRecorder {
+ public:
+  explicit WebRenderCompositionRecorder(TimeStamp aRecordingStart,
+                                        wr::PipelineId aRootPipelineId)
+      : CompositionRecorder(aRecordingStart),
+        mMutex("CompositionRecorder"),
+        mFinishedRecording(false),
+        mRootPipelineId(aRootPipelineId) {}
+
+  WebRenderCompositionRecorder() = delete;
+  WebRenderCompositionRecorder(WebRenderCompositionRecorder&) = delete;
+  WebRenderCompositionRecorder(WebRenderCompositionRecorder&&) = delete;
+
+  WebRenderCompositionRecorder& operator=(WebRenderCompositionRecorder&) =
+      delete;
+  WebRenderCompositionRecorder& operator=(WebRenderCompositionRecorder&&) =
+      delete;
+
+  /**
+   * Do not call this method.
+   *
+   * Instead, call |MaybeRecordFrame|, which will only attempt to record a
+   * frame if we have not yet written frames to disk.
+   */
+  void RecordFrame(RecordedFrame* aFrame) override;
+
+  /**
+   * Write the collected frames to disk.
+   *
+   * This method should not be called if frames have already been written or if
+   * |ForceFinishRecording| has been called as the object will be in an invalid
+   * state to write to disk.
+   *
+   * Note: This method will block acquiring a lock.
+   */
+  void WriteCollectedFrames() override;
+
+  /**
+   * Attempt to record a frame from the given renderer.
+   *
+   * This method will only record a frame if the following are true:
+   *
+   * - this object's lock was acquired immediately (i.e., we are not currently
+   *   writing frames to disk);
+   * - we have not yet written frames to disk; and
+   * - one of the pipelines in |aFrameEpochs| has updated and it is not the
+   *   root pipeline.
+   *
+   * Returns whether or not the recorder has finished recording frames. If
+   * true, it is safe to release both this object and Web Render's composition
+   * recorder structures.
+   */
+  bool MaybeRecordFrame(wr::Renderer* aRenderer,
+                        wr::WebRenderPipelineInfo* aFrameEpochs);
+
+ protected:
+  ~WebRenderCompositionRecorder() = default;
+
+  /**
+   * Determine if any content pipelines updated.
+   */
+  bool DidPaintContent(wr::WebRenderPipelineInfo* aFrameEpochs);
+
+ private:
+  Mutex mMutex;
+
+  // Whether or not we have finished recording.
+  bool mFinishedRecording;
+
+  // The id of the root WebRender pipeline.
+  //
+  // All other pipelines are considered content.
+  wr::PipelineId mRootPipelineId;
+
+  // A mapping of wr::PipelineId to the epochs when last they updated.
+  //
+  // We need to use uint64_t here since wr::PipelineId is not default
+  // constructable.
+  std::unordered_map<uint64_t, wr::Epoch> mContentPipelines;
+};
+
+}  // namespace layers
+}  // namespace mozilla
+
+#endif  // mozilla_layers_WebRenderCompositionRecorder_h
--- a/gfx/webrender_bindings/src/bindings.rs
+++ b/gfx/webrender_bindings/src/bindings.rs
@@ -69,16 +69,17 @@ pub enum AntialiasBorder {
 pub enum OpacityType {
     Opaque = 0,
     HasAlphaChannel = 1,
 }
 
 /// cbindgen:field-names=[mHandle]
 /// cbindgen:derive-lt=true
 /// cbindgen:derive-lte=true
+/// cbindgen:derive-neq=true
 type WrEpoch = Epoch;
 /// cbindgen:field-names=[mHandle]
 /// cbindgen:derive-lt=true
 /// cbindgen:derive-lte=true
 /// cbindgen:derive-neq=true
 pub type WrIdNamespace = IdNamespace;
 
 /// cbindgen:field-names=[mNamespace, mHandle]