Bug 1536174 - Part 1: Add API for recording composition results with content. r=mstange
authorBas Schouten <bschouten@mozilla.com>
Tue, 12 Mar 2019 16:06:48 +0100
changeset 466524 8186704e6d0301fc99812195fe9ecc737298344c
parent 466523 6e4c046bbac71dcceecdee932177d0fef04c7eaa
child 466525 ffb2e68a6445f991cc6802f0d01ddc209dff51dc
push id35769
push useropoprus@mozilla.com
push dateThu, 28 Mar 2019 10:12:29 +0000
treeherdermozilla-central@d2c82f845bfc [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmstange
bugs1536174
milestone68.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 1536174 - Part 1: Add API for recording composition results with content. r=mstange Differential Revision: https://phabricator.services.mozilla.com/D23913
gfx/layers/CompositionRecorder.cpp
gfx/layers/CompositionRecorder.h
gfx/layers/LayersTypes.h
gfx/layers/client/ClientLayerManager.cpp
gfx/layers/composite/LayerManagerComposite.h
gfx/layers/ipc/CompositorBridgeParent.cpp
gfx/layers/ipc/CompositorBridgeParent.h
gfx/layers/ipc/ContentCompositorBridgeParent.h
gfx/layers/ipc/PCompositorBridge.ipdl
gfx/layers/mlgpu/LayerManagerMLGPU.cpp
gfx/layers/moz.build
gfx/thebes/gfxPrefs.h
new file mode 100644
--- /dev/null
+++ b/gfx/layers/CompositionRecorder.cpp
@@ -0,0 +1,64 @@
+/* -*- 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 "CompositionRecorder.h"
+#include "gfxUtils.h"
+#include "mozilla/gfx/2D.h"
+#include "gfxPrefs.h"
+
+#include <ctime>
+#include <iomanip>
+#include "stdio.h"
+#ifdef XP_WIN
+#  include "direct.h"
+#else
+#  include <sys/types.h>
+#  include "sys/stat.h"
+#endif
+
+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;
+  str << gfxPrefs::LayersWindowRecordingPath() << "windowrecording-"
+      << std::put_time(&tm, "%Y%m%d%H%M%S");
+
+#ifdef XP_WIN
+  _mkdir(str.str().c_str());
+#else
+  mkdir(str.str().c_str(), 0777);
+#endif
+
+  uint32_t i = 1;
+  for (RefPtr<RecordedFrame>& frame : mCollectedFrames) {
+    RefPtr<DataSourceSurface> surf = frame->GetSourceSurface();
+    std::stringstream filename;
+    filename << str.str() << "/frame-" << i << "-"
+             << uint32_t(
+                    (frame->GetTimeStamp() - mRecordingStart).ToMilliseconds())
+             << ".png";
+    gfxUtils::WriteAsPNG(surf, filename.str().c_str());
+    i++;
+  }
+  mCollectedFrames.Clear();
+}
+
+}  // namespace layers
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/layers/CompositionRecorder.h
@@ -0,0 +1,60 @@
+/* -*- 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_CompositionRecorder_h
+#define mozilla_layers_CompositionRecorder_h
+
+#include "mozilla/RefPtr.h"
+#include "mozilla/TimeStamp.h"
+#include "nsISupportsImpl.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+
+namespace gfx {
+class DataSourceSurface;
+}
+
+namespace layers {
+
+class RecordedFrame {
+ public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RecordedFrame)
+
+  // The resulting DataSourceSurface must not be kept alive beyond the lifetime
+  // of the RecordedFrame object, since it may refer to data owned by the frame.
+  virtual already_AddRefed<gfx::DataSourceSurface> GetSourceSurface() = 0;
+  TimeStamp GetTimeStamp() { return mTimeStamp; }
+
+ protected:
+  virtual ~RecordedFrame() {}
+  RecordedFrame(const TimeStamp& aTimeStamp) : mTimeStamp(aTimeStamp) {}
+
+ private:
+  TimeStamp mTimeStamp;
+};
+
+/**
+ *
+ */
+class CompositionRecorder final {
+ public:
+  explicit CompositionRecorder(TimeStamp aRecordingStart);
+  ~CompositionRecorder();
+
+  void RecordFrame(RecordedFrame* aFrame);
+
+  void WriteCollectedFrames();
+
+ private:
+  nsTArray<RefPtr<RecordedFrame>> mCollectedFrames;
+  TimeStamp mRecordingStart;
+};
+
+}  // namespace layers
+}  // namespace mozilla
+
+#endif  // mozilla_layers_ProfilerScreenshots_h
--- a/gfx/layers/LayersTypes.h
+++ b/gfx/layers/LayersTypes.h
@@ -410,17 +410,18 @@ class CompositableHandle {
 MOZ_DEFINE_ENUM_CLASS_WITH_BASE(ScrollDirection, uint32_t, (
   eVertical,
   eHorizontal
 ));
 
 MOZ_DEFINE_ENUM_CLASS_WITH_BASE(CompositionPayloadType, uint8_t, (
   eKeyPress,
   eAPZScroll,
-  eAPZPinchZoom
+  eAPZPinchZoom,
+  eContentPaint
 ));
 // clang-format on
 
 struct CompositionPayload {
   bool operator==(const CompositionPayload& aOther) const {
     return mType == aOther.mType && mTimeStamp == aOther.mTimeStamp;
   }
   /* The type of payload that is in this composition */
--- a/gfx/layers/client/ClientLayerManager.cpp
+++ b/gfx/layers/client/ClientLayerManager.cpp
@@ -366,16 +366,19 @@ void ClientLayerManager::EndTransaction(
     mInTransaction = false;
     return;
   }
 
   if (mWidget) {
     mWidget->PrepareWindowEffects();
   }
   EndTransactionInternal(aCallback, aCallbackData, aFlags);
+  if (XRE_IsContentProcess()) {
+    RegisterPayload({CompositionPayloadType::eContentPaint, TimeStamp::Now()});
+  }
   ForwardTransaction(!(aFlags & END_NO_REMOTE_COMPOSITE));
 
   if (mRepeatTransaction) {
     mRepeatTransaction = false;
     mIsRepeatTransaction = true;
 
     // BeginTransaction will reset the transaction start time, but we
     // would like to keep the original time for telemetry purposes.
--- a/gfx/layers/composite/LayerManagerComposite.h
+++ b/gfx/layers/composite/LayerManagerComposite.h
@@ -48,16 +48,17 @@ namespace gfx {
 class DrawTarget;
 }  // namespace gfx
 
 namespace layers {
 
 class CanvasLayerComposite;
 class ColorLayerComposite;
 class Compositor;
+class CompositionRecorder;
 class ContainerLayerComposite;
 class Diagnostics;
 struct EffectChain;
 class ImageLayer;
 class ImageLayerComposite;
 class LayerComposite;
 class RefLayerComposite;
 class PaintedLayerComposite;
@@ -195,29 +196,34 @@ class HostLayerManager : public LayerMan
   // async compositables.
   uint64_t GetCompositorBridgeID() const { return mCompositorBridgeID; }
   void SetCompositorBridgeID(uint64_t aID) {
     MOZ_ASSERT(mCompositorBridgeID == 0,
                "The compositor ID must be set only once.");
     mCompositorBridgeID = aID;
   }
 
+  void SetCompositionRecorder(CompositionRecorder* aRecorder) {
+    mCompositionRecorder = aRecorder;
+  }
+
  protected:
   bool mDebugOverlayWantsNextFrame;
   nsTArray<ImageCompositeNotificationInfo> mImageCompositeNotifications;
   // Testing property. If hardware composer is supported, this will return
   // true if the last frame was deemed 'too complicated' to be rendered.
   float mWarningLevel;
   mozilla::TimeStamp mWarnTime;
   UniquePtr<Diagnostics> mDiagnostics;
   uint64_t mCompositorBridgeID;
 
   bool mWindowOverlayChanged;
   TimeDuration mLastPaintTime;
   TimeStamp mRenderStartTime;
+  CompositionRecorder* mCompositionRecorder = nullptr;
 
   // Render time for the current composition.
   TimeStamp mCompositionTime;
 
   // When nonnull, during rendering, some compositable indicated that it will
   // change its rendering at this time. In order not to miss it, we composite
   // on every vsync until this time occurs (this is the latest such time).
   TimeStamp mCompositeUntilTime;
--- a/gfx/layers/ipc/CompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/CompositorBridgeParent.cpp
@@ -40,16 +40,17 @@
 #include "mozilla/gfx/GPUParent.h"
 #include "mozilla/layers/AnimationHelper.h"  // for CompositorAnimationStorage
 #include "mozilla/layers/APZCTreeManagerParent.h"  // for APZCTreeManagerParent
 #include "mozilla/layers/APZSampler.h"             // for APZSampler
 #include "mozilla/layers/APZThreadUtils.h"         // for APZThreadUtils
 #include "mozilla/layers/APZUpdater.h"             // for APZUpdater
 #include "mozilla/layers/AsyncCompositionManager.h"
 #include "mozilla/layers/BasicCompositor.h"          // for BasicCompositor
+#include "mozilla/layers/CompositionRecorder.h"      // for CompositionRecorder
 #include "mozilla/layers/Compositor.h"               // for Compositor
 #include "mozilla/layers/CompositorManagerParent.h"  // for CompositorManagerParent
 #include "mozilla/layers/CompositorOGL.h"            // for CompositorOGL
 #include "mozilla/layers/CompositorThread.h"
 #include "mozilla/layers/CompositorTypes.h"
 #include "mozilla/layers/CompositorVsyncScheduler.h"
 #include "mozilla/layers/ContentCompositorBridgeParent.h"
 #include "mozilla/layers/FrameUniformityData.h"
@@ -2594,10 +2595,24 @@ int32_t RecordContentFrameTime(
                             fracLatencyNorm);
     }
     return result;
   }
 
   return 0;
 }
 
+mozilla::ipc::IPCResult CompositorBridgeParent::RecvBeginRecording(
+    const TimeStamp& aRecordingStart) {
+  mCompositionRecorder.reset(new CompositionRecorder(aRecordingStart));
+  mLayerManager->SetCompositionRecorder(mCompositionRecorder.get());
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult CompositorBridgeParent::RecvEndRecording() {
+  mLayerManager->SetCompositionRecorder(nullptr);
+  mCompositionRecorder->WriteCollectedFrames();
+  mCompositionRecorder.reset(nullptr);
+  return IPC_OK();
+}
+
 }  // namespace layers
 }  // namespace mozilla
--- a/gfx/layers/ipc/CompositorBridgeParent.h
+++ b/gfx/layers/ipc/CompositorBridgeParent.h
@@ -67,16 +67,17 @@ class ProtocolFuzzerHelper;
 namespace layers {
 
 class APZCTreeManager;
 class APZCTreeManagerParent;
 class APZSampler;
 class APZUpdater;
 class AsyncCompositionManager;
 class AsyncImagePipelineManager;
+class CompositionRecorder;
 class Compositor;
 class CompositorAnimationStorage;
 class CompositorBridgeParent;
 class CompositorManagerParent;
 class CompositorVsyncScheduler;
 class HostLayerManager;
 class IAPZCTreeManager;
 class LayerTransactionParent;
@@ -233,16 +234,19 @@ class CompositorBridgeParentBase : publi
   virtual mozilla::ipc::IPCResult RecvRemotePluginsReady() = 0;
   virtual mozilla::ipc::IPCResult RecvAdoptChild(const LayersId& id) = 0;
   virtual mozilla::ipc::IPCResult RecvFlushRenderingAsync() = 0;
   virtual mozilla::ipc::IPCResult RecvForcePresent() = 0;
   virtual mozilla::ipc::IPCResult RecvNotifyRegionInvalidated(
       const nsIntRegion& region) = 0;
   virtual mozilla::ipc::IPCResult RecvRequestNotifyAfterRemotePaint() = 0;
   virtual mozilla::ipc::IPCResult RecvAllPluginsCaptured() = 0;
+  virtual mozilla::ipc::IPCResult RecvBeginRecording(
+      const TimeStamp& aRecordingStart) = 0;
+  virtual mozilla::ipc::IPCResult RecvEndRecording() = 0;
   virtual mozilla::ipc::IPCResult RecvInitialize(
       const LayersId& rootLayerTreeId) = 0;
   virtual mozilla::ipc::IPCResult RecvGetFrameUniformity(
       FrameUniformityData* data) = 0;
   virtual mozilla::ipc::IPCResult RecvWillClose() = 0;
   virtual mozilla::ipc::IPCResult RecvPause() = 0;
   virtual mozilla::ipc::IPCResult RecvResume() = 0;
   virtual mozilla::ipc::IPCResult RecvNotifyChildCreated(
@@ -338,16 +342,19 @@ class CompositorBridgeParent final : pub
 
   // Unused for chrome <-> compositor communication (which this class does).
   // @see ContentCompositorBridgeParent::RecvRequestNotifyAfterRemotePaint
   mozilla::ipc::IPCResult RecvRequestNotifyAfterRemotePaint() override {
     return IPC_OK();
   };
 
   mozilla::ipc::IPCResult RecvAllPluginsCaptured() override;
+  mozilla::ipc::IPCResult RecvBeginRecording(
+      const TimeStamp& aRecordingStart) override;
+  mozilla::ipc::IPCResult RecvEndRecording() override;
 
   virtual void NotifyMemoryPressure() override;
   virtual void AccumulateMemoryReport(wr::MemoryReport*) override;
 
   void ActorDestroy(ActorDestroyReason why) override;
 
   void ShadowLayersUpdated(LayerTransactionParent* aLayerTree,
                            const TransactionInfo& aInfo,
@@ -757,16 +764,17 @@ class CompositorBridgeParent final : pub
   RefPtr<APZUpdater> mApzUpdater;
 
   RefPtr<CompositorVsyncScheduler> mCompositorScheduler;
   // This makes sure the compositorParent is not destroyed before receiving
   // confirmation that the channel is closed.
   // mSelfRef is cleared in DeferredDestroy which is scheduled by ActorDestroy.
   RefPtr<CompositorBridgeParent> mSelfRef;
   RefPtr<CompositorAnimationStorage> mAnimationStorage;
+  UniquePtr<CompositionRecorder> mCompositionRecorder;
 
   TimeDuration mPaintTime;
 
 #if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
   // cached plugin data used to reduce the number of updates we request.
   LayersId mLastPluginUpdateLayerTreeId;
   nsIntPoint mPluginsLayerOffset;
   nsIntRegion mPluginsLayerVisibleRegion;
--- a/gfx/layers/ipc/ContentCompositorBridgeParent.h
+++ b/gfx/layers/ipc/ContentCompositorBridgeParent.h
@@ -79,16 +79,22 @@ class ContentCompositorBridgeParent fina
     return IPC_OK();
   }
 
   mozilla::ipc::IPCResult RecvCheckContentOnlyTDR(
       const uint32_t& sequenceNum, bool* isContentOnlyTDR) override;
 
   mozilla::ipc::IPCResult RecvAllPluginsCaptured() override { return IPC_OK(); }
 
+  mozilla::ipc::IPCResult RecvBeginRecording(
+      const TimeStamp& aRecordingStart) override {
+    return IPC_OK();
+  }
+  mozilla::ipc::IPCResult RecvEndRecording() override { return IPC_OK(); }
+
   mozilla::ipc::IPCResult RecvGetFrameUniformity(
       FrameUniformityData* aOutData) override {
     // Don't support calculating frame uniformity on the child process and
     // this is just a stub for now.
     MOZ_ASSERT(false);
     return IPC_OK();
   }
 
--- a/gfx/layers/ipc/PCompositorBridge.ipdl
+++ b/gfx/layers/ipc/PCompositorBridge.ipdl
@@ -259,16 +259,19 @@ parent:
   sync SyncWithCompositor();
 
   // The pipelineId is the same as the layersId
   async PWebRenderBridge(PipelineId pipelineId, LayoutDeviceIntSize aSize);
 
   sync CheckContentOnlyTDR(uint32_t sequenceNum)
     returns (bool isContentOnlyTDR);
 
+  async BeginRecording(TimeStamp aRecordingStart);
+  async EndRecording();
+
 child:
   // Send back Compositor Frame Metrics from APZCs so tiled layers can
   // update progressively.
   async SharedCompositorFrameMetrics(Handle metrics, CrossProcessMutexHandle mutex, LayersId aLayersId, uint32_t aAPZCId);
   async ReleaseSharedCompositorFrameMetrics(ViewID aId, uint32_t aAPZCId);
   async NotifyWebRenderError(WebRenderError error);
 };
 
--- a/gfx/layers/mlgpu/LayerManagerMLGPU.cpp
+++ b/gfx/layers/mlgpu/LayerManagerMLGPU.cpp
@@ -17,16 +17,17 @@
 #include "ShaderDefinitionsMLGPU.h"
 #include "SharedBufferMLGPU.h"
 #include "UnitTransforms.h"
 #include "TextureSourceProviderMLGPU.h"
 #include "TreeTraversal.h"
 #include "FrameBuilder.h"
 #include "LayersLogging.h"
 #include "UtilityMLGPU.h"
+#include "CompositionRecorder.h"
 #include "mozilla/layers/Diagnostics.h"
 #include "mozilla/layers/TextRenderer.h"
 
 #ifdef XP_WIN
 #  include "mozilla/widget/WinCompositorWidget.h"
 #  include "mozilla/gfx/DeviceManagerDx.h"
 #endif
 
@@ -37,16 +38,59 @@ namespace layers {
 
 using namespace gfx;
 
 static const int kDebugOverlayX = 2;
 static const int kDebugOverlayY = 5;
 static const int kDebugOverlayMaxWidth = 600;
 static const int kDebugOverlayMaxHeight = 96;
 
+class RecordedFrameMLGPU : public RecordedFrame {
+ public:
+  RecordedFrameMLGPU(MLGDevice* aDevice, MLGTexture* aTexture,
+                     const TimeStamp& aTimestamp)
+      : RecordedFrame(aTimestamp), mDevice(aDevice) {
+    mSoftTexture =
+        aDevice->CreateTexture(aTexture->GetSize(), SurfaceFormat::B8G8R8A8,
+                               MLGUsage::Staging, MLGTextureFlags::None);
+
+    aDevice->CopyTexture(mSoftTexture, IntPoint(), aTexture,
+                         IntRect(IntPoint(), aTexture->GetSize()));
+  }
+
+  ~RecordedFrameMLGPU() {
+    if (mIsMapped) {
+      mDevice->Unmap(mSoftTexture);
+    }
+  }
+
+  virtual already_AddRefed<gfx::DataSourceSurface> GetSourceSurface() override {
+    if (mDataSurf) {
+      return RefPtr<DataSourceSurface>(mDataSurf).forget();
+    }
+    MLGMappedResource map;
+    if (!mDevice->Map(mSoftTexture, MLGMapType::READ, &map)) {
+      return nullptr;
+    }
+
+    mIsMapped = true;
+    mDataSurf = Factory::CreateWrappingDataSourceSurface(
+        map.mData, map.mStride, mSoftTexture->GetSize(),
+        SurfaceFormat::B8G8R8A8);
+    return RefPtr<DataSourceSurface>(mDataSurf).forget();
+  }
+
+ private:
+  RefPtr<MLGDevice> mDevice;
+  // Software texture in VRAM.
+  RefPtr<MLGTexture> mSoftTexture;
+  RefPtr<DataSourceSurface> mDataSurf;
+  bool mIsMapped = false;
+};
+
 LayerManagerMLGPU::LayerManagerMLGPU(widget::CompositorWidget* aWidget)
     : mWidget(aWidget),
       mDrawDiagnostics(false),
       mUsingInvalidation(false),
       mCurrentFrame(nullptr),
       mDebugFrameNumber(0) {
   if (!aWidget) {
     return;
@@ -351,16 +395,32 @@ void LayerManagerMLGPU::RenderLayers() {
     mDevice->StartDiagnostics(numPixels);
   }
 
   // Execute all render passes.
   builder.Render();
 
   mProfilerScreenshotGrabber.MaybeGrabScreenshot(
       mDevice, builder.GetWidgetRT()->GetTexture());
+
+  if (mCompositionRecorder) {
+    bool hasContentPaint = false;
+    for (CompositionPayload& payload : mPayload) {
+      if (payload.mType == CompositionPayloadType::eContentPaint) {
+        hasContentPaint = true;
+        break;
+      }
+    }
+
+    if (hasContentPaint) {
+      RefPtr<RecordedFrame> frame = new RecordedFrameMLGPU(
+          mDevice, builder.GetWidgetRT()->GetTexture(), TimeStamp::Now());
+      mCompositionRecorder->RecordFrame(frame);
+    }
+  }
   mCurrentFrame = nullptr;
 
   if (mDrawDiagnostics) {
     mDiagnostics->RecordCompositeTime(
         (TimeStamp::Now() - start).ToMilliseconds());
     mDevice->EndDiagnostics();
   }
 }
--- a/gfx/layers/moz.build
+++ b/gfx/layers/moz.build
@@ -156,16 +156,17 @@ EXPORTS.mozilla.layers += [
     'composite/ImageComposite.h',
     'composite/ImageHost.h',
     'composite/ImageLayerComposite.h',
     'composite/LayerManagerComposite.h',
     'composite/PaintedLayerComposite.h',
     'composite/TextRenderer.h',
     'composite/TextureHost.h',
     'composite/TiledContentHost.h',
+    'CompositionRecorder.h',
     'Compositor.h',
     'CompositorTypes.h',
     'CopyableCanvasRenderer.h',
     'D3D11ShareHandleImage.h',
     'D3D11YCbCrImage.h',
     'D3D9SurfaceImage.h',
     'DirectionUtils.h',
     'Effects.h',
@@ -404,16 +405,17 @@ UNIFIED_SOURCES += [
     'composite/ImageComposite.cpp',
     'composite/ImageHost.cpp',
     'composite/ImageLayerComposite.cpp',
     'composite/LayerManagerComposite.cpp',
     'composite/PaintedLayerComposite.cpp',
     'composite/TextRenderer.cpp',
     'composite/TextureHost.cpp',
     'composite/TiledContentHost.cpp',
+    'CompositionRecorder.cpp',
     'Compositor.cpp',
     'CopyableCanvasRenderer.cpp',
     'Effects.cpp',
     'FrameMetrics.cpp',
     'GLImages.cpp',
     'ImageDataSerializer.cpp',
     'ImageLayers.cpp',
     'ipc/APZChild.cpp',
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -664,16 +664,17 @@ class gfxPrefs final {
   DECL_GFX_PREF(Once, "layers.omtp.paint-workers",             LayersOMTPPaintWorkers, int32_t, 1);
   DECL_GFX_PREF(Live, "layers.omtp.release-capture-on-main-thread", LayersOMTPReleaseCaptureOnMainThread, bool, false);
   DECL_GFX_PREF(Live, "layers.orientation.sync.timeout",       OrientationSyncMillis, uint32_t, (uint32_t)0);
   DECL_GFX_PREF(Once, "layers.prefer-opengl",                  LayersPreferOpenGL, bool, false);
   DECL_GFX_PREF(Live, "layers.progressive-paint",              ProgressivePaint, bool, false);
   DECL_GFX_PREF(Live, "layers.shared-buffer-provider.enabled", PersistentBufferProviderSharedEnabled, bool, false);
   DECL_GFX_PREF(Live, "layers.single-tile.enabled",            LayersSingleTileEnabled, bool, true);
   DECL_GFX_PREF(Live, "layers.force-synchronous-resize",       LayersForceSynchronousResize, bool, true);
+  DECL_GFX_PREF(Once, "layers.windowrecording.path",           LayersWindowRecordingPath, std::string, std::string());
 
   // We allow for configurable and rectangular tile size to avoid wasting memory on devices whose
   // screen size does not align nicely to the default tile size. Although layers can be any size,
   // they are often the same size as the screen, especially for width.
   DECL_GFX_PREF(Once, "layers.tile-width",                     LayersTileWidth, int32_t, 256);
   DECL_GFX_PREF(Once, "layers.tile-height",                    LayersTileHeight, int32_t, 256);
   DECL_GFX_PREF(Once, "layers.tile-initial-pool-size",         LayersTileInitialPoolSize, uint32_t, (uint32_t)50);
   DECL_GFX_PREF(Once, "layers.tile-pool-unused-size",          LayersTilePoolUnusedSize, uint32_t, (uint32_t)10);