Bug 1600793 - Make the scrolling input telemetry work for WebRender r=botond,jrmuizel
authorSean Feng <sefeng@mozilla.com>
Thu, 12 Mar 2020 17:39:59 +0000
changeset 518473 b781fe3bde1aed855813cc97f7250885a3512a0c
parent 518472 abfdbbe3a8462859c55e044322b6b094bdc0be76
child 518474 c682703f7ffb83d78ccd34805ac81115747f59c8
push id109976
push usersefeng@mozilla.com
push dateThu, 12 Mar 2020 19:13:35 +0000
treeherderautoland@b781fe3bde1a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbotond, jrmuizel
bugs1600793
milestone76.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 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
@@ -697,19 +697,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;
@@ -722,16 +723,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
@@ -2343,16 +2343,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);
@@ -2363,16 +2370,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
@@ -337,17 +337,18 @@ WebRenderBridgeParent::WebRenderBridgePa
       mRenderRootRectMutex("WebRenderBridgeParent::mRenderRootRectMutex"),
 #if defined(MOZ_WIDGET_ANDROID)
       mScreenPixelsTarget(nullptr),
 #endif
       mPaused(false),
       mDestroyed(false),
       mReceivedDisplayList(false),
       mIsFirstPaint(true),
-      mSkippedComposite(false) {
+      mSkippedComposite(false),
+      mPendingScrollPayloads("WebRenderBridgeParent::mPendingScrollPayloads") {
   MOZ_ASSERT(mAsyncImageManager);
   MOZ_ASSERT(mAnimStorage);
   mAsyncImageManager->AddPipeline(mPipelineId, this);
   if (IsRootWebRenderBridgeParent()) {
     MOZ_ASSERT(!mCompositorScheduler);
     mCompositorScheduler = new CompositorVsyncScheduler(this, mWidget);
   }
 
@@ -372,17 +373,18 @@ WebRenderBridgeParent::WebRenderBridgePa
       mParentLayersObserverEpoch{0},
       mWrEpoch{0},
       mIdNamespace{0},
       mRenderRootRectMutex("WebRenderBridgeParent::mRenderRootRectMutex"),
       mPaused(false),
       mDestroyed(true),
       mReceivedDisplayList(false),
       mIsFirstPaint(false),
-      mSkippedComposite(false) {}
+      mSkippedComposite(false),
+      mPendingScrollPayloads("WebRenderBridgeParent::mPendingScrollPayloads") {}
 
 WebRenderBridgeParent::~WebRenderBridgeParent() {
   if (RefPtr<WebRenderBridgeParent> root = GetRootWebRenderBridgeParent()) {
     root->RemoveDeferredPipeline(mPipelineId);
   }
 }
 
 bool WebRenderBridgeParent::RenderRootIsValid(wr::RenderRoot aRenderRoot) {
@@ -1040,16 +1042,38 @@ 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) {
+  auto pendingScrollPayloads = mPendingScrollPayloads.Lock();
+  nsTArray<CompositionPayload>* payloads =
+      pendingScrollPayloads->LookupOrAdd(aKey);
+
+  payloads->AppendElement(aPayload);
+}
+
+nsTArray<CompositionPayload>* WebRenderBridgeParent::GetPendingScrollPayload(
+    const std::pair<wr::PipelineId, wr::Epoch>& aKey) {
+  auto pendingScrollPayloads = mPendingScrollPayloads.Lock();
+  return pendingScrollPayloads->Get(aKey);
+}
+
+bool WebRenderBridgeParent::RemovePendingScrollPayload(
+    const std::pair<wr::PipelineId, wr::Epoch>& aKey) {
+  auto pendingScrollPayloads = mPendingScrollPayloads.Lock();
+  return pendingScrollPayloads->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);
+
+  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) {
@@ -575,14 +620,18 @@ class WebRenderBridgeParent final
 #if defined(MOZ_WIDGET_ANDROID)
   UiCompositorControllerParent* mScreenPixelsTarget;
 #endif
   bool mPaused;
   bool mDestroyed;
   bool mReceivedDisplayList;
   bool mIsFirstPaint;
   bool mSkippedComposite;
+  // These payloads are being used for SCROLL_PRESENT_LATENCY telemetry
+  DataMutex<nsClassHashtable<PipelineIdAndEpochHashEntry,
+                             nsTArray<CompositionPayload>>>
+      mPendingScrollPayloads;
 };
 
 }  // namespace layers
 }  // namespace mozilla
 
 #endif  // mozilla_layers_WebRenderBridgeParent_h
--- a/gfx/webrender_bindings/src/bindings.rs
+++ b/gfx/webrender_bindings/src/bindings.rs
@@ -32,17 +32,17 @@ use num_cpus;
 use program_cache::{remove_disk_cache, WrProgramCache};
 use rayon;
 use thread_profiler::register_thread_with_profiler;
 use webrender::{
     api::*, api::units::*, ApiRecordingReceiver, AsyncPropertySampler, AsyncScreenshotHandle,
     BinaryRecorder, Compositor, CompositorCapabilities, 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
 };
 
 #[cfg(target_os = "macos")]
 use core_foundation::string::CFString;
 #[cfg(target_os = "macos")]
 use core_graphics::font::CGFont;
 
 extern "C" {
@@ -749,16 +749,33 @@ impl<'a> From<(&'a (WrPipelineId, WrDocu
             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 {
@@ -857,17 +874,17 @@ extern "C" {
     fn apz_pre_scene_swap(window_id: WrWindowId);
     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);
+    fn apz_sample_transforms(window_id: WrWindowId, transaction: &mut Transaction, document_id: WrDocumentId, epochs_being_rendered: &WrPipelineIdEpochs);
     fn apz_deregister_sampler(window_id: WrWindowId);
 }
 
 struct APZCallbacks {
     window_id: WrWindowId,
 }
 
 impl APZCallbacks {
@@ -942,19 +959,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
@@ -213,16 +213,17 @@ pub use crate::prim_store::PrimitiveDebu
 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::hit_test::SharedHitTester;
+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::ItemUid;
--- a/gfx/wr/webrender/src/render_backend.rs
+++ b/gfx/wr/webrender/src/render_backend.rs
@@ -1507,29 +1507,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;
 
         // TODO: this scroll variable doesn't necessarily mean we scrolled. It is only used
         // for something wrench specific and we should remove it.
         let mut scroll = false;
         for frame_msg in frame_ops {
             let _timer = profile_counters.total_time.timer();
             let op = doc.process_frame_msg(frame_msg);
--- a/gfx/wr/webrender/src/renderer.rs
+++ b/gfx/wr/webrender/src/renderer.rs
@@ -6506,17 +6506,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)]