Bug 1377619 - Add EndTransaction to on screen timing log for WebRender r=nical
authorsotaro <sotaro.ikeda.g@gmail.com>
Fri, 28 Jul 2017 08:05:56 +0900
changeset 420300 c140e16d478a78cd6e24c379c8fc75394cc6ff78
parent 420299 c8f400fc23a72fab985d3baa08b5cc22d2190326
child 420301 c8e2129968bfbf2afb293e72852c4b08cadcbda4
push id7566
push usermtabara@mozilla.com
push dateWed, 02 Aug 2017 08:25:16 +0000
treeherdermozilla-beta@86913f512c3c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnical
bugs1377619
milestone56.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 1377619 - Add EndTransaction to on screen timing log for WebRender r=nical
gfx/layers/ipc/CompositorBridgeParent.cpp
gfx/layers/ipc/PWebRenderBridge.ipdl
gfx/layers/wr/WebRenderBridgeChild.cpp
gfx/layers/wr/WebRenderBridgeParent.cpp
gfx/layers/wr/WebRenderBridgeParent.h
gfx/webrender_bindings/RendererOGL.cpp
gfx/webrender_bindings/RendererOGL.h
gfx/webrender_bindings/WebRenderAPI.cpp
gfx/webrender_bindings/WebRenderAPI.h
gfx/webrender_bindings/WebRenderTypes.h
--- a/gfx/layers/ipc/CompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/CompositorBridgeParent.cpp
@@ -1952,34 +1952,34 @@ CompositorBridgeParent::NotifyDidComposi
   }
   mWrBridge->CompositableHolder()->Update(aPipelineId, aEpoch);
 
   if (mPaused) {
     return;
   }
 
   if (mWrBridge->PipelineId() == aPipelineId) {
-    uint64_t transactionId = mWrBridge->FlushTransactionIdsForEpoch(aEpoch);
+    uint64_t transactionId = mWrBridge->FlushTransactionIdsForEpoch(aEpoch, aCompositeEnd);
     Unused << SendDidComposite(0, transactionId, aCompositeStart, aCompositeEnd);
 
     nsTArray<ImageCompositeNotificationInfo> notifications;
     mWrBridge->ExtractImageCompositeNotifications(&notifications);
     if (!notifications.IsEmpty()) {
       Unused << ImageBridgeParent::NotifyImageComposites(notifications);
     }
     return;
   }
 
   MonitorAutoLock lock(*sIndirectLayerTreesLock);
   ForEachIndirectLayerTree([&] (LayerTreeState* lts, const uint64_t& aLayersId) -> void {
     if (lts->mCrossProcessParent &&
         lts->mWrBridge &&
         lts->mWrBridge->PipelineId() == aPipelineId) {
       CrossProcessCompositorBridgeParent* cpcp = lts->mCrossProcessParent;
-      uint64_t transactionId = lts->mWrBridge->FlushTransactionIdsForEpoch(aEpoch);
+      uint64_t transactionId = lts->mWrBridge->FlushTransactionIdsForEpoch(aEpoch, aCompositeEnd);
       Unused << cpcp->SendDidComposite(aLayersId, transactionId, aCompositeStart, aCompositeEnd);
     }
   });
 }
 
 void
 CompositorBridgeParent::NotifyDidComposite(uint64_t aTransactionId, TimeStamp& aCompositeStart, TimeStamp& aCompositeEnd)
 {
--- a/gfx/layers/ipc/PWebRenderBridge.ipdl
+++ b/gfx/layers/ipc/PWebRenderBridge.ipdl
@@ -51,20 +51,20 @@ parent:
                    SurfaceFormat aFormat, ByteBuffer aBytes);
   async DeleteImage(ImageKey aImageKey);
   async DeleteCompositorAnimations(uint64_t[] aIds);
   async AddRawFont(FontKey aFontKey, ByteBuffer aBytes, uint32_t aFontIndex);
   async DeleteFont(FontKey aFontKey);
   async DPBegin(IntSize aSize);
   async DPEnd(IntSize aSize, WebRenderParentCommand[] commands, OpDestroy[] toDestroy, uint64_t fwdTransactionId, uint64_t transactionId,
               LayoutSize aContentSize, ByteBuffer aDL, BuiltDisplayListDescriptor aDLDesc,
-              WebRenderScrollData aScrollData, uint32_t idNameSpace);
+              WebRenderScrollData aScrollData, uint32_t idNameSpace, TimeStamp fwdTime);
   sync DPSyncEnd(IntSize aSize, WebRenderParentCommand[] commands, OpDestroy[] toDestroy, uint64_t fwdTransactionId, uint64_t transactionId,
                  LayoutSize aContentSize, ByteBuffer aDL, BuiltDisplayListDescriptor aDLDesc,
-                 WebRenderScrollData aScrollData, uint32_t idNameSpace);
+                 WebRenderScrollData aScrollData, uint32_t idNameSpace, TimeStamp fwdTime);
   async ParentCommands(WebRenderParentCommand[] commands);
   sync DPGetSnapshot(PTexture texture);
   async AddPipelineIdForAsyncCompositable(PipelineId aImageId, CompositableHandle aHandle);
   async RemovePipelineIdForAsyncCompositable(PipelineId aPipelineId);
   async AddExternalImageIdForCompositable(ExternalImageId aImageId, CompositableHandle aHandle);
   async RemoveExternalImageId(ExternalImageId aImageId);
   async SetLayerObserverEpoch(uint64_t layerObserverEpoch);
   async ClearCachedResources();
--- a/gfx/layers/wr/WebRenderBridgeChild.cpp
+++ b/gfx/layers/wr/WebRenderBridgeChild.cpp
@@ -107,22 +107,27 @@ WebRenderBridgeChild::DPEnd(wr::DisplayL
   MOZ_ASSERT(!mDestroyed);
   MOZ_ASSERT(mIsInTransaction);
 
   wr::BuiltDisplayList dl;
   wr::LayoutSize contentSize;
   aBuilder.Finalize(contentSize, dl);
   ByteBuffer dlData(Move(dl.dl));
 
+  TimeStamp fwdTime;
+#if defined(ENABLE_FRAME_LATENCY_LOG)
+  fwdTime = TimeStamp::Now();
+#endif
+
   if (aIsSync) {
     this->SendDPSyncEnd(aSize, mParentCommands, mDestroyedActors, GetFwdTransactionId(), aTransactionId,
-                        contentSize, dlData, dl.dl_desc, aScrollData, mIdNamespace);
+                        contentSize, dlData, dl.dl_desc, aScrollData, mIdNamespace,fwdTime);
   } else {
     this->SendDPEnd(aSize, mParentCommands, mDestroyedActors, GetFwdTransactionId(), aTransactionId,
-                    contentSize, dlData, dl.dl_desc, aScrollData, mIdNamespace);
+                    contentSize, dlData, dl.dl_desc, aScrollData, mIdNamespace, fwdTime);
   }
 
   mParentCommands.Clear();
   mDestroyedActors.Clear();
   mIsInTransaction = false;
 }
 
 void
--- a/gfx/layers/wr/WebRenderBridgeParent.cpp
+++ b/gfx/layers/wr/WebRenderBridgeParent.cpp
@@ -399,17 +399,18 @@ WebRenderBridgeParent::HandleDPEnd(const
                                  InfallibleTArray<WebRenderParentCommand>&& aCommands,
                                  InfallibleTArray<OpDestroy>&& aToDestroy,
                                  const uint64_t& aFwdTransactionId,
                                  const uint64_t& aTransactionId,
                                  const wr::LayoutSize& aContentSize,
                                  const wr::ByteBuffer& dl,
                                  const wr::BuiltDisplayListDescriptor& dlDesc,
                                  const WebRenderScrollData& aScrollData,
-                                 const uint32_t& aIdNameSpace)
+                                 const uint32_t& aIdNameSpace,
+                                 const TimeStamp& aFwdTime)
 {
   AutoProfilerTracing tracing("Paint", "DPTransaction");
   UpdateFwdTransactionId(aFwdTransactionId);
   AutoClearReadLocks clearLocks(mReadLocks);
 
   if (mDestroyed) {
     for (const auto& op : aToDestroy) {
       DestroyActor(op);
@@ -418,17 +419,17 @@ WebRenderBridgeParent::HandleDPEnd(const
   }
   // This ensures that destroy operations are always processed. It is not safe
   // to early-return from RecvDPEnd without doing so.
   AutoWebRenderBridgeParentAsyncMessageSender autoAsyncMessageSender(this, &aToDestroy);
 
   uint32_t wrEpoch = GetNextWrEpoch();
   ProcessWebRenderCommands(aSize, aCommands, wr::NewEpoch(wrEpoch),
                            aContentSize, dl, dlDesc, aIdNameSpace);
-  HoldPendingTransactionId(wrEpoch, aTransactionId);
+  HoldPendingTransactionId(wrEpoch, aTransactionId, aFwdTime);
 
   mScrollData = aScrollData;
   UpdateAPZ();
 
   if (mIdNameSpace != aIdNameSpace) {
     // Pretend we composited since someone is wating for this event,
     // though DisplayList was not pushed to webrender.
     TimeStamp now = TimeStamp::Now();
@@ -512,43 +513,45 @@ WebRenderBridgeParent::RecvDPEnd(const g
                                  InfallibleTArray<WebRenderParentCommand>&& aCommands,
                                  InfallibleTArray<OpDestroy>&& aToDestroy,
                                  const uint64_t& aFwdTransactionId,
                                  const uint64_t& aTransactionId,
                                  const wr::LayoutSize& aContentSize,
                                  const wr::ByteBuffer& dl,
                                  const wr::BuiltDisplayListDescriptor& dlDesc,
                                  const WebRenderScrollData& aScrollData,
-                                 const uint32_t& aIdNameSpace)
+                                 const uint32_t& aIdNameSpace,
+                                 const TimeStamp& aFwdTime)
 {
   if (mDestroyed) {
     return IPC_OK();
   }
   HandleDPEnd(aSize, Move(aCommands), Move(aToDestroy), aFwdTransactionId, aTransactionId,
-              aContentSize, dl, dlDesc, aScrollData, aIdNameSpace);
+              aContentSize, dl, dlDesc, aScrollData, aIdNameSpace, aFwdTime);
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 WebRenderBridgeParent::RecvDPSyncEnd(const gfx::IntSize &aSize,
                                      InfallibleTArray<WebRenderParentCommand>&& aCommands,
                                      InfallibleTArray<OpDestroy>&& aToDestroy,
                                      const uint64_t& aFwdTransactionId,
                                      const uint64_t& aTransactionId,
                                      const wr::LayoutSize& aContentSize,
                                      const wr::ByteBuffer& dl,
                                      const wr::BuiltDisplayListDescriptor& dlDesc,
                                      const WebRenderScrollData& aScrollData,
-                                     const uint32_t& aIdNameSpace)
+                                     const uint32_t& aIdNameSpace,
+                                     const TimeStamp& aFwdTime)
 {
   if (mDestroyed) {
     return IPC_OK();
   }
   HandleDPEnd(aSize, Move(aCommands), Move(aToDestroy), aFwdTransactionId, aTransactionId,
-              aContentSize, dl, dlDesc, aScrollData, aIdNameSpace);
+              aContentSize, dl, dlDesc, aScrollData, aIdNameSpace, aFwdTime);
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 WebRenderBridgeParent::RecvParentCommands(nsTArray<WebRenderParentCommand>&& aCommands)
 {
   if (mDestroyed) {
     return IPC_OK();
@@ -1119,43 +1122,48 @@ WebRenderBridgeParent::CompositeToTarget
   }
 
   if (PushAPZStateToWR(transformArray)) {
     scheduleComposite = true;
   }
 
   wr::RenderThread::Get()->IncPendingFrameCount(mApi->GetId());
 
+#if defined(ENABLE_FRAME_LATENCY_LOG)
+  auto startTime = TimeStamp::Now();
+  mApi->SetFrameStartTime(startTime);
+#endif
+
   if (!transformArray.IsEmpty() || !opacityArray.IsEmpty()) {
     mApi->GenerateFrame(opacityArray, transformArray);
   } else {
     mApi->GenerateFrame();
   }
 
   if (!mCompositableHolder->GetCompositeUntilTime().IsNull()) {
     scheduleComposite = true;
   }
 
   if (scheduleComposite) {
     ScheduleComposition();
   }
 }
 
 void
-WebRenderBridgeParent::HoldPendingTransactionId(uint32_t aWrEpoch, uint64_t aTransactionId)
+WebRenderBridgeParent::HoldPendingTransactionId(uint32_t aWrEpoch, uint64_t aTransactionId, const TimeStamp& aFwdTime)
 {
   // The transaction ID might get reset to 1 if the page gets reloaded, see
   // https://bugzilla.mozilla.org/show_bug.cgi?id=1145295#c41
   // Otherwise, it should be continually increasing.
   MOZ_ASSERT(aTransactionId == 1 || aTransactionId > LastPendingTransactionId());
   // Handle TransactionIdAllocator(RefreshDriver) change.
   if (aTransactionId == 1) {
     FlushPendingTransactionIds();
   }
-  mPendingTransactionIds.push(PendingTransactionId(wr::NewEpoch(aWrEpoch), aTransactionId));
+  mPendingTransactionIds.push(PendingTransactionId(wr::NewEpoch(aWrEpoch), aTransactionId, aFwdTime));
 }
 
 uint64_t
 WebRenderBridgeParent::LastPendingTransactionId()
 {
   uint64_t id = 0;
   if (!mPendingTransactionIds.empty()) {
     id = mPendingTransactionIds.back().mId;
@@ -1170,25 +1178,32 @@ WebRenderBridgeParent::FlushPendingTrans
   while (!mPendingTransactionIds.empty()) {
     id = mPendingTransactionIds.front().mId;
     mPendingTransactionIds.pop();
   }
   return id;
 }
 
 uint64_t
-WebRenderBridgeParent::FlushTransactionIdsForEpoch(const wr::Epoch& aEpoch)
+WebRenderBridgeParent::FlushTransactionIdsForEpoch(const wr::Epoch& aEpoch, const TimeStamp& aEndTime)
 {
   uint64_t id = 0;
   while (!mPendingTransactionIds.empty()) {
     int64_t diff =
       static_cast<int64_t>(aEpoch.mHandle) - static_cast<int64_t>(mPendingTransactionIds.front().mEpoch.mHandle);
     if (diff < 0) {
       break;
     }
+#if defined(ENABLE_FRAME_LATENCY_LOG)
+    if (mPendingTransactionIds.front().mFwdTime) {
+      uint32_t latencyMs = round((aEndTime - mPendingTransactionIds.front().mFwdTime).ToMilliseconds());
+      printf_stderr("From EndTransaction to end of generate frame latencyMs %d this %p\n", latencyMs, this);
+    }
+#endif
+
     id = mPendingTransactionIds.front().mId;
     mPendingTransactionIds.pop();
     if (diff == 0) {
       break;
     }
   }
   return id;
 }
--- a/gfx/layers/wr/WebRenderBridgeParent.h
+++ b/gfx/layers/wr/WebRenderBridgeParent.h
@@ -97,27 +97,29 @@ public:
                                     InfallibleTArray<WebRenderParentCommand>&& aCommands,
                                     InfallibleTArray<OpDestroy>&& aToDestroy,
                                     const uint64_t& aFwdTransactionId,
                                     const uint64_t& aTransactionId,
                                     const wr::LayoutSize& aContentSize,
                                     const wr::ByteBuffer& dl,
                                     const wr::BuiltDisplayListDescriptor& dlDesc,
                                     const WebRenderScrollData& aScrollData,
-                                    const uint32_t& aIdNameSpace) override;
+                                    const uint32_t& aIdNameSpace,
+                                    const TimeStamp& aFwdTime) override;
   mozilla::ipc::IPCResult RecvDPSyncEnd(const gfx::IntSize& aSize,
                                         InfallibleTArray<WebRenderParentCommand>&& aCommands,
                                         InfallibleTArray<OpDestroy>&& aToDestroy,
                                         const uint64_t& aFwdTransactionId,
                                         const uint64_t& aTransactionId,
                                         const wr::LayoutSize& aContentSize,
                                         const wr::ByteBuffer& dl,
                                         const wr::BuiltDisplayListDescriptor& dlDesc,
                                         const WebRenderScrollData& aScrollData,
-                                        const uint32_t& aIdNameSpace) override;
+                                        const uint32_t& aIdNameSpace,
+                                        const TimeStamp& aFwdTime) override;
   mozilla::ipc::IPCResult RecvParentCommands(nsTArray<WebRenderParentCommand>&& commands) override;
   mozilla::ipc::IPCResult RecvDPGetSnapshot(PTextureParent* aTexture) override;
 
   mozilla::ipc::IPCResult RecvAddPipelineIdForAsyncCompositable(const wr::PipelineId& aPipelineIds,
                                                                 const CompositableHandle& aHandle) override;
   mozilla::ipc::IPCResult RecvRemovePipelineIdForAsyncCompositable(const wr::PipelineId& aPipelineId) override;
 
   mozilla::ipc::IPCResult RecvAddExternalImageIdForCompositable(const ExternalImageId& aImageId,
@@ -162,20 +164,20 @@ public:
   // CompositableParentManager
   bool IsSameProcess() const override;
   base::ProcessId GetChildProcessId() override;
   void NotifyNotUsed(PTextureParent* aTexture, uint64_t aTransactionId) override;
   void SendAsyncMessage(const InfallibleTArray<AsyncParentMessageData>& aMessage) override;
   void SendPendingAsyncMessages() override;
   void SetAboutToSendAsyncMessages() override;
 
-  void HoldPendingTransactionId(uint32_t aWrEpoch, uint64_t aTransactionId);
+  void HoldPendingTransactionId(uint32_t aWrEpoch, uint64_t aTransactionId, const TimeStamp& aFwdTime);
   uint64_t LastPendingTransactionId();
   uint64_t FlushPendingTransactionIds();
-  uint64_t FlushTransactionIdsForEpoch(const wr::Epoch& aEpoch);
+  uint64_t FlushTransactionIdsForEpoch(const wr::Epoch& aEpoch, const TimeStamp& aEndTime);
 
   TextureFactoryIdentifier GetTextureFactoryIdentifier();
 
   void AppendImageCompositeNotification(const ImageCompositeNotificationInfo& aNotification)
   {
     MOZ_ASSERT(mWidget);
     mImageCompositeNotifications.AppendElement(aNotification);
   }
@@ -228,17 +230,18 @@ private:
                    InfallibleTArray<WebRenderParentCommand>&& aCommands,
                    InfallibleTArray<OpDestroy>&& aToDestroy,
                    const uint64_t& aFwdTransactionId,
                    const uint64_t& aTransactionId,
                    const wr::LayoutSize& aContentSize,
                    const wr::ByteBuffer& dl,
                    const wr::BuiltDisplayListDescriptor& dlDesc,
                    const WebRenderScrollData& aScrollData,
-                   const uint32_t& aIdNameSpace);
+                   const uint32_t& aIdNameSpace,
+                   const TimeStamp& aFwdTime);
   mozilla::ipc::IPCResult HandleShutdown();
 
   void AdvanceAnimations();
   void SampleAnimations(nsTArray<wr::WrOpacityProperty>& aOpacityArray,
                         nsTArray<wr::WrTransformProperty>& aTransformArray);
 
   CompositorBridgeParent* GetRootCompositorBridgeParent() const;
 
@@ -251,22 +254,24 @@ private:
   // Helper method to get an APZC reference from a scroll id. Uses the layers
   // id of this bridge, and may return null if the APZC wasn't found.
   already_AddRefed<AsyncPanZoomController> GetTargetAPZC(const FrameMetrics::ViewID& aId);
 
   uint32_t GetNextWrEpoch();
 
 private:
   struct PendingTransactionId {
-    PendingTransactionId(wr::Epoch aEpoch, uint64_t aId)
+    PendingTransactionId(wr::Epoch aEpoch, uint64_t aId, const TimeStamp& aFwdTime)
       : mEpoch(aEpoch)
       , mId(aId)
+      , mFwdTime(aFwdTime)
     {}
     wr::Epoch mEpoch;
     uint64_t mId;
+    TimeStamp mFwdTime;
   };
 
   CompositorBridgeParentBase* MOZ_NON_OWNING_REF mCompositorBridge;
   wr::PipelineId mPipelineId;
   RefPtr<widget::CompositorWidget> mWidget;
   RefPtr<wr::WebRenderAPI> mApi;
   RefPtr<WebRenderCompositableHolder> mCompositableHolder;
   RefPtr<CompositorVsyncScheduler> mCompositorScheduler;
--- a/gfx/webrender_bindings/RendererOGL.cpp
+++ b/gfx/webrender_bindings/RendererOGL.cpp
@@ -127,16 +127,25 @@ RendererOGL::Render()
   // XXX set clear color if MOZ_WIDGET_ANDROID is defined.
 
   auto size = mWidget->GetClientSize();
   wr_renderer_render(mRenderer, size.width, size.height);
 
   mGL->SwapBuffers();
   mWidget->PostRender(&widgetContext);
 
+#if defined(ENABLE_FRAME_LATENCY_LOG)
+  if (mFrameStartTime) {
+    uint32_t latencyMs = round((TimeStamp::Now() - mFrameStartTime).ToMilliseconds());
+    printf_stderr("generate frame latencyMs latencyMs %d\n", latencyMs);
+  }
+  // Clear frame start time
+  mFrameStartTime = TimeStamp();
+#endif
+
   // TODO: Flush pending actions such as texture deletions/unlocks and
   //       textureHosts recycling.
 
   return true;
 }
 
 void
 RendererOGL::Pause()
@@ -165,16 +174,27 @@ RendererOGL::Resume()
 }
 
 void
 RendererOGL::SetProfilerEnabled(bool aEnabled)
 {
   wr_renderer_set_profiler_enabled(mRenderer, aEnabled);
 }
 
+void
+RendererOGL::SetFrameStartTime(const TimeStamp& aTime)
+{
+  if (mFrameStartTime) {
+    // frame start time is already set. This could happen when multiple
+    // generate frame requests are merged by webrender.
+    return;
+  }
+  mFrameStartTime = aTime;
+}
+
 wr::WrRenderedEpochs*
 RendererOGL::FlushRenderedEpochs()
 {
   return wr_renderer_flush_rendered_epochs(mRenderer);
 }
 
 RenderTextureHost*
 RendererOGL::GetRenderTexture(wr::WrExternalImageId aExternalImageId)
--- a/gfx/webrender_bindings/RendererOGL.h
+++ b/gfx/webrender_bindings/RendererOGL.h
@@ -54,16 +54,19 @@ public:
 
   /// This can be called on the render thread only.
   bool RenderToTarget(gfx::DrawTarget& aTarget);
 
   /// This can be called on the render thread only.
   void SetProfilerEnabled(bool aEnabled);
 
   /// This can be called on the render thread only.
+  void SetFrameStartTime(const TimeStamp& aTime);
+
+  /// This can be called on the render thread only.
   ~RendererOGL();
 
   /// This can be called on the render thread only.
   RendererOGL(RefPtr<RenderThread>&& aThread,
               RefPtr<gl::GLContext>&& aGL,
               RefPtr<widget::CompositorWidget>&&,
               wr::WindowId aWindowId,
               wr::Renderer* aRenderer,
@@ -86,14 +89,15 @@ public:
 protected:
 
   RefPtr<RenderThread> mThread;
   RefPtr<gl::GLContext> mGL;
   RefPtr<widget::CompositorWidget> mWidget;
   wr::Renderer* mRenderer;
   layers::CompositorBridgeParentBase* mBridge;
   wr::WindowId mWindowId;
+  TimeStamp mFrameStartTime;
 };
 
 } // namespace wr
 } // namespace mozilla
 
 #endif
--- a/gfx/webrender_bindings/WebRenderAPI.cpp
+++ b/gfx/webrender_bindings/WebRenderAPI.cpp
@@ -505,16 +505,49 @@ private:
 
 void
 WebRenderAPI::SetProfilerEnabled(bool aEnabled)
 {
   auto event = MakeUnique<EnableProfiler>(aEnabled);
   RunOnRenderThread(Move(event));
 }
 
+class FrameStartTime : public RendererEvent
+{
+public:
+  explicit FrameStartTime(const TimeStamp& aTime)
+    : mTime(aTime)
+  {
+    MOZ_COUNT_CTOR(FrameStartTime);
+  }
+
+  ~FrameStartTime()
+  {
+    MOZ_COUNT_DTOR(FrameStartTime);
+  }
+
+  virtual void Run(RenderThread& aRenderThread, WindowId aWindowId) override
+  {
+    auto renderer = aRenderThread.GetRenderer(aWindowId);
+    if (renderer) {
+      renderer->SetFrameStartTime(mTime);
+    }
+  }
+
+private:
+  TimeStamp mTime;
+};
+
+void
+WebRenderAPI::SetFrameStartTime(const TimeStamp& aTime)
+{
+  auto event = MakeUnique<FrameStartTime>(aTime);
+  RunOnRenderThread(Move(event));
+}
+
 void
 WebRenderAPI::RunOnRenderThread(UniquePtr<RendererEvent> aEvent)
 {
   auto event = reinterpret_cast<uintptr_t>(aEvent.release());
   wr_api_send_external_event(mRenderApi, event);
 }
 
 DisplayListBuilder::DisplayListBuilder(PipelineId aId,
--- a/gfx/webrender_bindings/WebRenderAPI.h
+++ b/gfx/webrender_bindings/WebRenderAPI.h
@@ -96,16 +96,18 @@ public:
   void DeleteImage(wr::ImageKey aKey);
 
   void AddRawFont(wr::FontKey aKey, Range<uint8_t> aBytes, uint32_t aIndex);
 
   void DeleteFont(wr::FontKey aKey);
 
   void SetProfilerEnabled(bool aEnabled);
 
+  void SetFrameStartTime(const TimeStamp& aTime);
+
   void RunOnRenderThread(UniquePtr<RendererEvent> aEvent);
   void Readback(gfx::IntSize aSize, uint8_t *aBuffer, uint32_t aBufferSize);
 
   void Pause();
   bool Resume();
 
   wr::WrIdNamespace GetNamespace();
   GLint GetMaxTextureSize() const { return mMaxTextureSize; }
--- a/gfx/webrender_bindings/WebRenderTypes.h
+++ b/gfx/webrender_bindings/WebRenderTypes.h
@@ -12,16 +12,18 @@
 #include "mozilla/gfx/Types.h"
 #include "mozilla/gfx/Tools.h"
 #include "mozilla/layers/LayersTypes.h"
 #include "mozilla/Range.h"
 #include "Units.h"
 #include "RoundedRect.h"
 #include "nsStyleConsts.h"
 
+//#define ENABLE_FRAME_LATENCY_LOG
+
 namespace mozilla {
 namespace wr {
 
 typedef wr::WrWindowId WindowId;
 typedef wr::WrPipelineId PipelineId;
 typedef wr::WrImageKey ImageKey;
 typedef wr::WrFontKey FontKey;
 typedef wr::WrEpoch Epoch;