Bug 1600793 - Make the scrolling input telemetry work for WebRender r=botond,jrmuizel
authorSean Feng <sefeng@mozilla.com>
Fri, 21 Feb 2020 19:41:46 +0000
changeset 2648945 8665af6e62a69fa2a7fb5de84294f8947986d25d
parent 2648944 4ea178fcab982876a95b35cf0ee0ed02f834cdb8
child 2648946 9011d5fe7e8441475155e1f31aeb922cbaad10dc
push id490874
push userdvarga@mozilla.com
push dateSat, 22 Feb 2020 10:42:44 +0000
treeherdertry@64a8e247759e [default view] [failures only]
reviewersbotond, jrmuizel
bugs1600793
milestone75.0a1
Bug 1600793 - Make the scrolling input telemetry work for WebRender r=botond,jrmuizel Differential Revision: https://phabricator.services.mozilla.com/D60046
gfx/layers/apz/public/APZSampler.h
gfx/layers/apz/src/APZCTreeManager.cpp
gfx/layers/apz/src/APZCTreeManager.h
gfx/layers/apz/src/APZSampler.cpp
gfx/layers/ipc/CompositorBridgeParent.cpp
gfx/layers/wr/WebRenderBridgeParent.cpp
gfx/layers/wr/WebRenderBridgeParent.h
gfx/webrender_bindings/src/bindings.rs
gfx/webrender_bindings/webrender_ffi.h
gfx/wr/webrender/src/lib.rs
gfx/wr/webrender/src/render_backend.rs
gfx/wr/webrender/src/renderer.rs
--- a/gfx/layers/apz/public/APZSampler.h
+++ b/gfx/layers/apz/public/APZSampler.h
@@ -53,23 +53,25 @@ class APZSampler {
 
   /**
    * This function is invoked from rust on the render backend thread when it
    * is created. It effectively tells the APZSampler "the current thread is
    * the sampler thread for this window id" and allows APZSampler to remember
    * which thread it is.
    */
   static void SetSamplerThread(const wr::WrWindowId& aWindowId);
-  static void SampleForWebRender(const wr::WrWindowId& aWindowId,
-                                 wr::Transaction* aTxn,
-                                 const wr::DocumentId& aRenderRootId);
+  static void SampleForWebRender(
+      const wr::WrWindowId& aWindowId, wr::Transaction* aTxn,
+      const wr::DocumentId& aRenderRootId,
+      const wr::WrPipelineIdEpochs* aEpochsBeingRendered);
 
   void SetSampleTime(const TimeStamp& aSampleTime);
   void SampleForWebRender(wr::TransactionWrapper& aTxn,
-                          wr::RenderRoot aRenderRoot);
+                          wr::RenderRoot aRenderRoot,
+                          const wr::WrPipelineIdEpochs* aEpochsBeingRendered);
 
   bool SampleAnimations(const LayerMetricsWrapper& aLayer,
                         const TimeStamp& aSampleTime);
 
   /**
    * Compute the updated shadow transform for a scroll thumb layer that
    * reflects async scrolling of the associated scroll frame.
    *
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -675,19 +675,20 @@ void APZCTreeManager::UpdateHitTestingTr
     const WebRenderScrollDataWrapper& aScrollWrapper, bool aIsFirstPaint,
     WRRootId aOriginatingWrRootId, uint32_t aPaintSequenceNumber) {
   AssertOnUpdaterThread();
 
   UpdateHitTestingTreeImpl(aScrollWrapper, aIsFirstPaint, aOriginatingWrRootId,
                            aPaintSequenceNumber);
 }
 
-void APZCTreeManager::SampleForWebRender(wr::TransactionWrapper& aTxn,
-                                         const TimeStamp& aSampleTime,
-                                         wr::RenderRoot aRenderRoot) {
+void APZCTreeManager::SampleForWebRender(
+    wr::TransactionWrapper& aTxn, const TimeStamp& aSampleTime,
+    wr::RenderRoot aRenderRoot,
+    const wr::WrPipelineIdEpochs* aEpochsBeingRendered) {
   AssertOnSamplerThread();
   MutexAutoLock lock(mMapLock);
 
   nsTArray<wr::WrTransformProperty> transforms;
 
   // Sample async transforms on scrollable layers.
   for (const auto& mapping : mApzcMap) {
     AsyncPanZoomController* apzc = mapping.second;
@@ -700,16 +701,37 @@ void APZCTreeManager::SampleForWebRender
         apzc->GetZoomAnimationId()
             ? AsyncTransformComponents{AsyncTransformComponent::eLayout}
             : LayoutAndVisual;
     ParentLayerPoint layerTranslation =
         apzc->GetCurrentAsyncTransform(AsyncPanZoomController::eForCompositing,
                                        asyncTransformComponents)
             .mTranslation;
 
+    if (Maybe<CompositionPayload> payload = apzc->NotifyScrollSampling()) {
+      RefPtr<WebRenderBridgeParent> wrBridgeParent;
+      LayersId layersId = apzc->GetGuid().mLayersId;
+      CompositorBridgeParent::CallWithIndirectShadowTree(
+          layersId, [&](LayerTreeState& aState) -> void {
+            wrBridgeParent = aState.mWrBridge;
+          });
+
+      if (wrBridgeParent) {
+        wr::PipelineId pipelineId = wr::AsPipelineId(layersId);
+        for (size_t i = 0; i < aEpochsBeingRendered->Length(); i++) {
+          if ((*aEpochsBeingRendered)[i].pipeline_id == pipelineId) {
+            auto& epoch = (*aEpochsBeingRendered)[i].epoch;
+            wrBridgeParent->AddPendingScrollPayload(
+                *payload, std::make_pair(pipelineId, epoch));
+            break;
+          }
+        }
+      }
+    }
+
     if (Maybe<uint64_t> zoomAnimationId = apzc->GetZoomAnimationId()) {
       // for now we only support zooming on root content APZCs
       MOZ_ASSERT(apzc->IsRootContent());
 
       LayoutDeviceToParentLayerScale zoom =
         apzc->GetCurrentPinchZoomScale(AsyncPanZoomController::eForCompositing);
 
       AsyncTransform asyncVisualTransform = apzc->GetCurrentAsyncTransform(
--- a/gfx/layers/apz/src/APZCTreeManager.h
+++ b/gfx/layers/apz/src/APZCTreeManager.h
@@ -197,17 +197,18 @@ class APZCTreeManager : public IAPZCTree
    * AsyncCompositionManager.
    * In the WebRender world a single "layer tree" might get split into multiple
    * render roots; the aRenderRoot argument indicates which render root we are
    * sampling in this call. The transaction should only be updated with samples
    * from APZC instances in that render root.
    */
   void SampleForWebRender(wr::TransactionWrapper& aTxn,
                           const TimeStamp& aSampleTime,
-                          wr::RenderRoot aRenderRoot);
+                          wr::RenderRoot aRenderRoot,
+                          const wr::WrPipelineIdEpochs* aEpochsBeingRendered);
 
   /**
    * Refer to the documentation of APZInputBridge::ReceiveInputEvent() and
    * APZEventResult.
    */
   APZEventResult ReceiveInputEvent(InputData& aEvent) override;
 
   /**
--- a/gfx/layers/apz/src/APZSampler.cpp
+++ b/gfx/layers/apz/src/APZSampler.cpp
@@ -60,45 +60,48 @@ void APZSampler::SetWebRenderWindowId(co
 void APZSampler::SetSamplerThread(const wr::WrWindowId& aWindowId) {
   if (RefPtr<APZSampler> sampler = GetSampler(aWindowId)) {
     MutexAutoLock lock(sampler->mThreadIdLock);
     sampler->mSamplerThreadId = Some(PlatformThread::CurrentId());
   }
 }
 
 /*static*/
-void APZSampler::SampleForWebRender(const wr::WrWindowId& aWindowId,
-                                    wr::Transaction* aTransaction,
-                                    const wr::DocumentId& aRenderRootId) {
+void APZSampler::SampleForWebRender(
+    const wr::WrWindowId& aWindowId, wr::Transaction* aTransaction,
+    const wr::DocumentId& aRenderRootId,
+    const wr::WrPipelineIdEpochs* aEpochsBeingRendered) {
   if (RefPtr<APZSampler> sampler = GetSampler(aWindowId)) {
     wr::TransactionWrapper txn(aTransaction);
-    sampler->SampleForWebRender(txn, wr::RenderRootFromId(aRenderRootId));
+    sampler->SampleForWebRender(txn, wr::RenderRootFromId(aRenderRootId),
+                                aEpochsBeingRendered);
   }
 }
 
 void APZSampler::SetSampleTime(const TimeStamp& aSampleTime) {
   MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
   MutexAutoLock lock(mSampleTimeLock);
   mSampleTime = aSampleTime;
 }
 
-void APZSampler::SampleForWebRender(wr::TransactionWrapper& aTxn,
-                                    wr::RenderRoot aRenderRoot) {
+void APZSampler::SampleForWebRender(
+    wr::TransactionWrapper& aTxn, wr::RenderRoot aRenderRoot,
+    const wr::WrPipelineIdEpochs* aEpochsBeingRendered) {
   AssertOnSamplerThread();
   TimeStamp sampleTime;
   {  // scope lock
     MutexAutoLock lock(mSampleTimeLock);
 
     // If mSampleTime is null we're in a startup phase where the
     // WebRenderBridgeParent hasn't yet provided us with a sample time.
     // If we're that early there probably aren't any APZ animations happening
     // anyway, so using Timestamp::Now() should be fine.
     sampleTime = mSampleTime.IsNull() ? TimeStamp::Now() : mSampleTime;
   }
-  mApz->SampleForWebRender(aTxn, sampleTime, aRenderRoot);
+  mApz->SampleForWebRender(aTxn, sampleTime, aRenderRoot, aEpochsBeingRendered);
 }
 
 bool APZSampler::SampleAnimations(const LayerMetricsWrapper& aLayer,
                                   const TimeStamp& aSampleTime) {
   MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
   AssertOnSamplerThread();
 
   // TODO: eventually we can drop the aLayer argument and just walk the APZ
@@ -261,16 +264,17 @@ already_AddRefed<APZSampler> APZSampler:
 
 }  // namespace layers
 }  // namespace mozilla
 
 void apz_register_sampler(mozilla::wr::WrWindowId aWindowId) {
   mozilla::layers::APZSampler::SetSamplerThread(aWindowId);
 }
 
-void apz_sample_transforms(mozilla::wr::WrWindowId aWindowId,
-                           mozilla::wr::Transaction* aTransaction,
-                           mozilla::wr::DocumentId aDocumentId) {
-  mozilla::layers::APZSampler::SampleForWebRender(aWindowId, aTransaction,
-                                                  aDocumentId);
+void apz_sample_transforms(
+    mozilla::wr::WrWindowId aWindowId, mozilla::wr::Transaction* aTransaction,
+    mozilla::wr::DocumentId aDocumentId,
+    const mozilla::wr::WrPipelineIdEpochs* aEpochsBeingRendered) {
+  mozilla::layers::APZSampler::SampleForWebRender(
+      aWindowId, aTransaction, aDocumentId, aEpochsBeingRendered);
 }
 
 void apz_deregister_sampler(mozilla::wr::WrWindowId aWindowId) {}
--- a/gfx/layers/ipc/CompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/CompositorBridgeParent.cpp
@@ -2347,16 +2347,23 @@ void CompositorBridgeParent::NotifyPipel
     mWrBridge->RemoveEpochDataPriorTo(aEpoch);
 
     if (!mPaused) {
       if (mIsForcedFirstPaint) {
         uiController->NotifyFirstPaint();
         mIsForcedFirstPaint = false;
       }
 
+      std::pair<wr::PipelineId, wr::Epoch> key(aPipelineId, aEpoch);
+      if (nsTArray<CompositionPayload>* payloads =
+              mWrBridge->GetPendingScrollPayload(key)) {
+        RecordCompositionPayloadsPresented(*payloads);
+        mWrBridge->RemovePendingScrollPayload(key);
+      }
+
       TransactionId transactionId = mWrBridge->FlushTransactionIdsForEpoch(
           aEpoch, aCompositeStartId, aCompositeStart, aRenderStart,
           aCompositeEnd, uiController);
       Unused << SendDidComposite(LayersId{0}, transactionId, aCompositeStart,
                                  aCompositeEnd);
 
       nsTArray<ImageCompositeNotificationInfo> notifications;
       mWrBridge->ExtractImageCompositeNotifications(&notifications);
@@ -2367,16 +2374,23 @@ void CompositorBridgeParent::NotifyPipel
     return;
   }
 
   auto wrBridge = mAsyncImageManager->GetWrBridge(aPipelineId);
   if (wrBridge && wrBridge->GetCompositorBridge()) {
     MOZ_ASSERT(!wrBridge->IsRootWebRenderBridgeParent());
     wrBridge->RemoveEpochDataPriorTo(aEpoch);
     if (!mPaused) {
+      std::pair<wr::PipelineId, wr::Epoch> key(aPipelineId, aEpoch);
+      if (nsTArray<CompositionPayload>* payloads =
+              wrBridge->GetPendingScrollPayload(key)) {
+        RecordCompositionPayloadsPresented(*payloads);
+        wrBridge->RemovePendingScrollPayload(key);
+      }
+
       TransactionId transactionId = wrBridge->FlushTransactionIdsForEpoch(
           aEpoch, aCompositeStartId, aCompositeStart, aRenderStart,
           aCompositeEnd, uiController, aStats, &stats);
       Unused << wrBridge->GetCompositorBridge()->SendDidComposite(
           wrBridge->GetLayersId(), transactionId, aCompositeStart,
           aCompositeEnd);
     }
   }
--- a/gfx/layers/wr/WebRenderBridgeParent.cpp
+++ b/gfx/layers/wr/WebRenderBridgeParent.cpp
@@ -1042,16 +1042,34 @@ WebRenderBridgeParent::WriteCollectedFra
   return Api(wr::RenderRoot::Default)->WriteCollectedFrames();
 }
 
 RefPtr<wr::WebRenderAPI::GetCollectedFramesPromise>
 WebRenderBridgeParent::GetCollectedFrames() {
   return Api(wr::RenderRoot::Default)->GetCollectedFrames();
 }
 
+void WebRenderBridgeParent::AddPendingScrollPayload(
+    CompositionPayload& aPayload,
+    const std::pair<wr::PipelineId, wr::Epoch>& aKey) {
+  nsTArray<CompositionPayload>* payloads =
+      mPendingScrollPayloads.LookupOrAdd(aKey);
+  payloads->AppendElement(aPayload);
+}
+
+nsTArray<CompositionPayload>* WebRenderBridgeParent::GetPendingScrollPayload(
+    const std::pair<wr::PipelineId, wr::Epoch>& aKey) const {
+  return mPendingScrollPayloads.Get(aKey);
+}
+
+bool WebRenderBridgeParent::RemovePendingScrollPayload(
+    const std::pair<wr::PipelineId, wr::Epoch>& aKey) {
+  return mPendingScrollPayloads.Remove(aKey);
+}
+
 CompositorBridgeParent* WebRenderBridgeParent::GetRootCompositorBridgeParent()
     const {
   if (!mCompositorBridge) {
     return nullptr;
   }
 
   if (IsRootWebRenderBridgeParent()) {
     // This WebRenderBridgeParent is attached to the root
--- a/gfx/layers/wr/WebRenderBridgeParent.h
+++ b/gfx/layers/wr/WebRenderBridgeParent.h
@@ -47,16 +47,52 @@ namespace layers {
 
 class Compositor;
 class CompositorAnimationStorage;
 class CompositorBridgeParentBase;
 class CompositorVsyncScheduler;
 class AsyncImagePipelineManager;
 class WebRenderImageHost;
 
+class PipelineIdAndEpochHashEntry : public PLDHashEntryHdr {
+ public:
+  typedef const std::pair<wr::PipelineId, wr::Epoch>& KeyType;
+  typedef const std::pair<wr::PipelineId, wr::Epoch>* KeyTypePointer;
+  enum { ALLOW_MEMMOVE = true };
+
+  explicit PipelineIdAndEpochHashEntry(wr::PipelineId aPipelineId,
+                                       wr::Epoch aEpoch)
+      : mValue(aPipelineId, aEpoch) {}
+
+  PipelineIdAndEpochHashEntry(PipelineIdAndEpochHashEntry&& aOther) = default;
+
+  explicit PipelineIdAndEpochHashEntry(KeyTypePointer aKey)
+    : mValue(aKey->first, aKey->second) {}
+
+  ~PipelineIdAndEpochHashEntry() {}
+
+  KeyType GetKey() const { return mValue; }
+
+  bool KeyEquals(KeyTypePointer aKey) const {
+    return mValue.first.mHandle == aKey->first.mHandle &&
+           mValue.first.mNamespace == aKey->first.mNamespace &&
+           mValue.second.mHandle == aKey->second.mHandle;
+  };
+
+  static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
+
+  static PLDHashNumber HashKey(KeyTypePointer aKey) {
+    return mozilla::HashGeneric(aKey->first.mHandle, aKey->first.mNamespace,
+                                aKey->second.mHandle);
+  }
+
+ private:
+  std::pair<wr::PipelineId, wr::Epoch> mValue;
+};
+
 class WebRenderBridgeParent final
     : public PWebRenderBridgeParent,
       public CompositorVsyncSchedulerOwner,
       public CompositableParentManager,
       public layers::FrameRecorder,
       public SupportsWeakPtr<WebRenderBridgeParent> {
  public:
   MOZ_DECLARE_WEAKREFERENCE_TYPENAME(WebRenderBridgeParent)
@@ -315,16 +351,25 @@ class WebRenderBridgeParent final
    * as data URIs.
    *
    * If there is not currently a recorder, this is a no-op and the promise will
    * be rejected.
    */
   RefPtr<wr::WebRenderAPI::GetCollectedFramesPromise> GetCollectedFrames();
 
   void DisableNativeCompositor();
+  void AddPendingScrollPayload(
+      CompositionPayload& aPayload,
+      const std::pair<wr::PipelineId, wr::Epoch>& aKey);
+
+  nsTArray<CompositionPayload>* GetPendingScrollPayload(
+      const std::pair<wr::PipelineId, wr::Epoch>& aKey) const;
+
+  bool RemovePendingScrollPayload(
+      const std::pair<wr::PipelineId, wr::Epoch>& aKey);
 
  private:
   class ScheduleSharedSurfaceRelease;
 
   explicit WebRenderBridgeParent(const wr::PipelineId& aPipelineId);
   virtual ~WebRenderBridgeParent();
 
   wr::WebRenderAPI* Api(wr::RenderRoot aRenderRoot) {
@@ -553,16 +598,20 @@ class WebRenderBridgeParent final
   // These fields keep track of the latest layer observer epoch values in the
   // child and the parent. mChildLayersObserverEpoch is the latest epoch value
   // received from the child. mParentLayersObserverEpoch is the latest epoch
   // value that we have told BrowserParent about (via ObserveLayerUpdate).
   LayersObserverEpoch mChildLayersObserverEpoch;
   LayersObserverEpoch mParentLayersObserverEpoch;
 
   std::deque<PendingTransactionId> mPendingTransactionIds;
+  // These payloads are being used for SCROLL_PRESENT_LATENCY telemetry
+  nsClassHashtable<PipelineIdAndEpochHashEntry, nsTArray<CompositionPayload>>
+      mPendingScrollPayloads;
+
   std::queue<CompositorAnimationIdsForEpoch> mCompositorAnimationsToDelete;
   wr::Epoch mWrEpoch;
   wr::IdNamespace mIdNamespace;
 
   VsyncId mSkippedCompositeId;
   TimeStamp mMostRecentComposite;
 
   // Kind of clunky, but I can't sort out a more elegant way of getting this to
--- a/gfx/webrender_bindings/src/bindings.rs
+++ b/gfx/webrender_bindings/src/bindings.rs
@@ -25,17 +25,17 @@ use std::time::Duration;
 use gleam::gl;
 use thin_vec::ThinVec;
 
 use webrender::{
     api::*, api::units::*, ApiRecordingReceiver, AsyncPropertySampler, AsyncScreenshotHandle,
     BinaryRecorder, Compositor, DebugFlags, Device,
     NativeSurfaceId, PipelineInfo, ProfilerHooks, RecordedFrameHandle, Renderer, RendererOptions, RendererStats,
     SceneBuilderHooks, ShaderPrecacheFlags, Shaders, ThreadListener, UploadMethod, VertexUsageHint,
-    WrShaders, set_profiler_hooks, CompositorConfig, NativeSurfaceInfo, NativeTileId
+    WrShaders, set_profiler_hooks, CompositorConfig, NativeSurfaceInfo, NativeTileId, FastHashMap
 };
 use thread_profiler::register_thread_with_profiler;
 use moz2d_renderer::Moz2dBlobImageHandler;
 use program_cache::{WrProgramCache, remove_disk_cache};
 use rayon;
 use num_cpus;
 use euclid::SideOffsets2D;
 use nsstring::nsAString;
@@ -762,16 +762,33 @@ impl<'a> From<(&'a(WrPipelineId, WrDocum
             pipeline_id: (tuple.0).0,
             document_id: (tuple.0).1,
             epoch: *tuple.1
         }
     }
 }
 
 #[repr(C)]
+pub struct WrPipelineIdAndEpoch {
+    pipeline_id: WrPipelineId,
+    epoch: WrEpoch
+}
+
+impl<'a> From<(&WrPipelineId, &WrEpoch)> for WrPipelineIdAndEpoch {
+    fn from(tuple: (&WrPipelineId, &WrEpoch)) -> WrPipelineIdAndEpoch {
+        WrPipelineIdAndEpoch {
+            pipeline_id: *tuple.0,
+            epoch: *tuple.1,
+        }
+    }
+}
+
+type WrPipelineIdEpochs = ThinVec<WrPipelineIdAndEpoch>;
+
+#[repr(C)]
 pub struct WrRemovedPipeline {
     pipeline_id: WrPipelineId,
     document_id: WrDocumentId,
 }
 
 impl<'a> From<&'a (WrPipelineId, WrDocumentId)> for WrRemovedPipeline {
     fn from(tuple: &(WrPipelineId, WrDocumentId)) -> WrRemovedPipeline {
         WrRemovedPipeline {
@@ -865,17 +882,18 @@ extern "C" {
     fn apz_post_scene_swap(window_id: WrWindowId, pipeline_info: &WrPipelineInfo);
     fn apz_run_updater(window_id: WrWindowId);
     fn apz_deregister_updater(window_id: WrWindowId);
 
     // These callbacks are invoked from the render backend thread (aka the APZ
     // sampler thread)
     fn apz_register_sampler(window_id: WrWindowId);
     fn apz_sample_transforms(window_id: WrWindowId, transaction: &mut Transaction,
-                             document_id: WrDocumentId);
+                             document_id: WrDocumentId,
+                             epochs_being_rendered: &WrPipelineIdEpochs);
     fn apz_deregister_sampler(window_id: WrWindowId);
 }
 
 struct APZCallbacks {
     window_id: WrWindowId,
 }
 
 impl APZCallbacks {
@@ -946,19 +964,24 @@ impl SamplerCallback {
     }
 }
 
 impl AsyncPropertySampler for SamplerCallback {
     fn register(&self) {
         unsafe { apz_register_sampler(self.window_id) }
     }
 
-    fn sample(&self, document_id: DocumentId) -> Vec<FrameMsg> {
+    fn sample(&self, document_id: DocumentId,
+              epochs_being_rendered: &FastHashMap<PipelineId, Epoch>) -> Vec<FrameMsg> {
         let mut transaction = Transaction::new();
-        unsafe { apz_sample_transforms(self.window_id, &mut transaction, document_id) };
+        unsafe { apz_sample_transforms(
+	    self.window_id, &mut transaction,
+            document_id, &epochs_being_rendered.iter()
+                                               .map(WrPipelineIdAndEpoch::from).collect()
+	)};
         // TODO: also omta_sample_transforms(...)
         transaction.get_frame_ops()
     }
 
     fn deregister(&self) {
         unsafe { apz_deregister_sampler(self.window_id) }
     }
 }
--- a/gfx/webrender_bindings/webrender_ffi.h
+++ b/gfx/webrender_bindings/webrender_ffi.h
@@ -68,32 +68,36 @@ struct InternerSubReport {
 
 #undef DECLARE_MEMBER
 
 struct Transaction;
 struct WrWindowId;
 struct DocumentId;
 struct WrPipelineInfo;
 
+struct WrPipelineIdAndEpoch;
+using WrPipelineIdEpochs = nsTArray<WrPipelineIdAndEpoch>;
+
 const uint64_t ROOT_CLIP_CHAIN = ~0;
 
 }  // namespace wr
 }  // namespace mozilla
 
 void apz_register_updater(mozilla::wr::WrWindowId aWindowId);
 void apz_pre_scene_swap(mozilla::wr::WrWindowId aWindowId);
 void apz_post_scene_swap(mozilla::wr::WrWindowId aWindowId,
                          const mozilla::wr::WrPipelineInfo* aInfo);
 void apz_run_updater(mozilla::wr::WrWindowId aWindowId);
 void apz_deregister_updater(mozilla::wr::WrWindowId aWindowId);
 
 void apz_register_sampler(mozilla::wr::WrWindowId aWindowId);
 void apz_sample_transforms(mozilla::wr::WrWindowId aWindowId,
                            mozilla::wr::Transaction* aTransaction,
-                           mozilla::wr::DocumentId aRenderRootId);
+                           mozilla::wr::DocumentId aRenderRootId,
+                           const mozilla::wr::WrPipelineIdEpochs* aPipelineEpochs);
 void apz_deregister_sampler(mozilla::wr::WrWindowId aWindowId);
 }  // extern "C"
 
 // Work-around wingdi.h define which conflcits with WR color constant
 #pragma push_macro("TRANSPARENT")
 #undef TRANSPARENT
 
 #include "webrender_ffi_generated.h"
--- a/gfx/wr/webrender/src/lib.rs
+++ b/gfx/wr/webrender/src/lib.rs
@@ -212,16 +212,17 @@ pub use crate::frame_builder::ChasePrimi
 pub use crate::prim_store::PrimitiveDebugId;
 pub use crate::profiler::{ProfilerHooks, set_profiler_hooks};
 pub use crate::renderer::{
     AsyncPropertySampler, CpuProfile, DebugFlags, RendererKind, GpuProfile, GraphicsApi,
     GraphicsApiInfo, PipelineInfo, Renderer, RendererError, RendererOptions, RenderResults,
     RendererStats, SceneBuilderHooks, ThreadListener, ShaderPrecacheFlags,
     MAX_VERTEX_TEXTURE_WIDTH,
 };
+pub use crate::internal_types::FastHashMap;
 pub use crate::screen_capture::{AsyncScreenshotHandle, RecordedFrameHandle};
 pub use crate::shade::{Shaders, WrShaders};
 pub use api as webrender_api;
 pub use webrender_build::shader::ProgramSourceDigest;
 pub use crate::picture::{TileDescriptor, TileId, InvalidationReason};
 pub use crate::picture::{PrimitiveCompareResult, PrimitiveCompareResultDetail, CompareHelperResult};
 pub use crate::picture::{TileNode, TileNodeKind, TileSerializer, TileCacheInstanceSerializer, TileOffset, TileCacheLoggerUpdateLists};
 pub use crate::intern::{Update, UpdateKind, ItemUid};
--- a/gfx/wr/webrender/src/render_backend.rs
+++ b/gfx/wr/webrender/src/render_backend.rs
@@ -1484,29 +1484,30 @@ impl RenderBackend {
         mut render_frame: bool,
         invalidate_rendered_frame: bool,
         frame_counter: &mut u32,
         profile_counters: &mut BackendProfileCounters,
         has_built_scene: bool,
     ) {
         let requested_frame = render_frame;
 
+        let requires_frame_build = self.requires_frame_build();
+        let doc = self.documents.get_mut(&document_id).unwrap();
         // If we have a sampler, get more frame ops from it and add them
         // to the transaction. This is a hook to allow the WR user code to
         // fiddle with things after a potentially long scene build, but just
         // before rendering. This is useful for rendering with the latest
         // async transforms.
         if requested_frame || has_built_scene {
             if let Some(ref sampler) = self.sampler {
-                frame_ops.append(&mut sampler.sample(document_id));
+                frame_ops.append(&mut sampler.sample(document_id,
+                                                     &doc.scene.pipeline_epochs));
             }
         }
 
-        let requires_frame_build = self.requires_frame_build();
-        let doc = self.documents.get_mut(&document_id).unwrap();
         doc.has_built_scene |= has_built_scene;
 
         // If there are any additions or removals of clip modes
         // during the scene build, apply them to the data store now.
         if let Some(updates) = interner_updates {
             #[cfg(feature = "capture")]
             {
                 if self.debug_flags.contains(DebugFlags::TILE_CACHE_LOGGING_DBG) {
--- a/gfx/wr/webrender/src/renderer.rs
+++ b/gfx/wr/webrender/src/renderer.rs
@@ -6226,17 +6226,18 @@ pub trait SceneBuilderHooks {
 /// are all called from the render backend thread.
 pub trait AsyncPropertySampler {
     /// This is called exactly once, when the render backend thread is started
     /// and before it processes anything.
     fn register(&self);
     /// This is called for each transaction with the generate_frame flag set
     /// (i.e. that will trigger a render). The list of frame messages returned
     /// are processed as though they were part of the original transaction.
-    fn sample(&self, document_id: DocumentId) -> Vec<FrameMsg>;
+    fn sample(&self, document_id: DocumentId,
+              doc: &FastHashMap<PipelineId, Epoch>) -> Vec<FrameMsg>;
     /// This is called exactly once, when the render backend thread is about to
     /// terminate.
     fn deregister(&self);
 }
 
 bitflags! {
     /// Flags that control how shaders are pre-cached, if at all.
     #[derive(Default)]