Bug 1294121 - Develop a continuous paint mode for Firefox. r=mattwoodrow
authorMason Chang <mchang@mozilla.com>
Tue, 16 Aug 2016 08:46:13 -0700
changeset 353135 635d266614ada43cf4a5b81a0bfc0310be966a18
parent 353134 0f604bac16ca3e263c0aa1666b4008ef1524760f
child 353136 9b2061eabc081e9a6fb272c2885d88792ca538a6
push id1324
push usermtabara@mozilla.com
push dateMon, 16 Jan 2017 13:07:44 +0000
treeherdermozilla-release@a01c49833940 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmattwoodrow
bugs1294121
milestone51.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 1294121 - Develop a continuous paint mode for Firefox. r=mattwoodrow
gfx/layers/client/ClientLayerManager.cpp
gfx/layers/client/ClientLayerManager.h
gfx/layers/composite/FPSCounter.cpp
gfx/layers/composite/LayerManagerComposite.cpp
gfx/layers/composite/LayerManagerComposite.h
gfx/layers/composite/PaintCounter.cpp
gfx/layers/composite/PaintCounter.h
gfx/layers/ipc/CompositorBridgeParent.cpp
gfx/layers/ipc/CompositorBridgeParent.h
gfx/layers/ipc/LayerTransactionParent.cpp
gfx/layers/ipc/LayerTransactionParent.h
gfx/layers/ipc/PLayerTransaction.ipdl
gfx/layers/ipc/ShadowLayers.cpp
gfx/layers/ipc/ShadowLayers.h
gfx/layers/ipc/ShadowLayersManager.h
gfx/layers/moz.build
gfx/thebes/gfxPrefs.h
layout/base/nsDisplayList.cpp
layout/base/nsRefreshDriver.cpp
modules/libpref/init/all.js
--- a/gfx/layers/client/ClientLayerManager.cpp
+++ b/gfx/layers/client/ClientLayerManager.cpp
@@ -88,16 +88,17 @@ ClientLayerManager::MemoryPressureObserv
 }
 
 NS_IMPL_ISUPPORTS(ClientLayerManager::MemoryPressureObserver, nsIObserver)
 
 ClientLayerManager::ClientLayerManager(nsIWidget* aWidget)
   : mPhase(PHASE_NONE)
   , mWidget(aWidget)
   , mLatestTransactionId(0)
+  , mLastPaintTime(TimeDuration::Forever())
   , mTargetRotation(ROTATION_0)
   , mRepeatTransaction(false)
   , mIsRepeatTransaction(false)
   , mTransactionIncomplete(false)
   , mCompositorMightResample(false)
   , mNeedsComposite(false)
   , mPaintSequenceNumber(0)
   , mForwarder(new ShadowLayerForwarder(this))
@@ -278,27 +279,34 @@ ClientLayerManager::EndTransactionIntern
   profiler_tracing("Paint", "Rasterize", TRACING_INTERVAL_START);
 
   NS_ASSERTION(InConstruction(), "Should be in construction phase");
   mPhase = PHASE_DRAWING;
 
   ClientLayer* root = ClientLayer::ToClientLayer(GetRoot());
 
   mTransactionIncomplete = false;
-      
+
   // Apply pending tree updates before recomputing effective
   // properties.
   GetRoot()->ApplyPendingUpdatesToSubtree();
-    
+
   mPaintedLayerCallback = aCallback;
   mPaintedLayerCallbackData = aCallbackData;
 
   GetRoot()->ComputeEffectiveTransforms(Matrix4x4());
 
-  root->RenderLayer();
+  if (gfxPrefs::AlwaysPaint() && XRE_IsContentProcess()) {
+    TimeStamp start = TimeStamp::Now();
+    root->RenderLayer();
+    mLastPaintTime = TimeStamp::Now() - start;
+  } else {
+    root->RenderLayer();
+  }
+
   if (!mRepeatTransaction && !GetRoot()->GetInvalidRegion().IsEmpty()) {
     GetRoot()->Mutated();
   }
 
   if (!mIsRepeatTransaction) {
     mAnimationReadyTime = TimeStamp::Now();
     GetRoot()->StartPendingAnimations(mAnimationReadyTime);
   }
@@ -618,16 +626,20 @@ ClientLayerManager::ForwardTransaction(b
   mLatestTransactionId = mTransactionIdAllocator->GetTransactionId();
   TimeStamp transactionStart;
   if (!mTransactionIdAllocator->GetTransactionStart().IsNull()) {
     transactionStart = mTransactionIdAllocator->GetTransactionStart();
   } else {
     transactionStart = mTransactionStart;
   }
 
+  if (gfxPrefs::AlwaysPaint() && XRE_IsContentProcess()) {
+    mForwarder->SendPaintTime(mLatestTransactionId, mLastPaintTime);
+  }
+
   // forward this transaction's changeset to our LayerManagerComposite
   bool sent;
   AutoTArray<EditReply, 10> replies;
   if (mForwarder->EndTransaction(&replies, mRegionToClear,
         mLatestTransactionId, aScheduleComposite, mPaintSequenceNumber,
         mIsRepeatTransaction, transactionStart, &sent)) {
     for (nsTArray<EditReply>::size_type i = 0; i < replies.Length(); ++i) {
       const EditReply& reply = replies[i];
--- a/gfx/layers/client/ClientLayerManager.h
+++ b/gfx/layers/client/ClientLayerManager.h
@@ -298,17 +298,17 @@ private:
                               void* aCallbackData,
                               EndTransactionFlags);
 
   bool DependsOnStaleDevice() const;
 
   LayerRefArray mKeepAlive;
 
   nsIWidget* mWidget;
-  
+
   /* PaintedLayer callbacks; valid at the end of a transaciton,
    * while rendering */
   DrawPaintedLayerCallback mPaintedLayerCallback;
   void *mPaintedLayerCallbackData;
 
   // When we're doing a transaction in order to draw to a non-default
   // target, the layers transaction is only performed in order to send
   // a PLayers:Update.  We save the original non-default target to
@@ -316,16 +316,17 @@ private:
   // mDummyTarget as the render target.  After the transaction ends,
   // we send a message to our remote side to capture the actual pixels
   // being drawn to the default target, and then copy those pixels
   // back to mShadowTarget.
   RefPtr<gfxContext> mShadowTarget;
 
   RefPtr<TransactionIdAllocator> mTransactionIdAllocator;
   uint64_t mLatestTransactionId;
+  TimeDuration mLastPaintTime;
 
   // Sometimes we draw to targets that don't natively support
   // landscape/portrait orientation.  When we need to implement that
   // ourselves, |mTargetRotation| describes the induced transform we
   // need to apply when compositing content to our target.
   ScreenRotation mTargetRotation;
 
   // Used to repeat the transaction right away (to avoid rebuilding
--- a/gfx/layers/composite/FPSCounter.cpp
+++ b/gfx/layers/composite/FPSCounter.cpp
@@ -389,18 +389,17 @@ static void DrawDigits(unsigned int aVal
     unsigned int digit = aValue % (divisor * 10) / divisor;
     divisor /= 10;
 
     RefPtr<TexturedEffect> texturedEffect = static_cast<TexturedEffect*>(aEffectChain.mPrimaryEffect.get());
     texturedEffect->mTextureCoords = Rect(float(digit * FontWidth) / textureWidth, 0, FontWidth / textureWidth, 1.0f);
 
     Rect drawRect = Rect(aOffsetX + n * FontWidth, aOffsetY, FontWidth, FontHeight);
     IntRect clipRect = IntRect(0, 0, 300, 100);
-    aCompositor->DrawQuad(drawRect, clipRect,
-  aEffectChain, opacity, transform);
+    aCompositor->DrawQuad(drawRect, clipRect, aEffectChain, opacity, transform);
   }
 }
 
 void FPSState::DrawFPS(TimeStamp aNow,
                        int aOffsetX, int aOffsetY,
                        unsigned int aFillRatio,
                        Compositor* aCompositor)
 {
--- a/gfx/layers/composite/LayerManagerComposite.cpp
+++ b/gfx/layers/composite/LayerManagerComposite.cpp
@@ -7,16 +7,17 @@
 #include <stddef.h>                     // for size_t
 #include <stdint.h>                     // for uint16_t, uint32_t
 #include "CanvasLayerComposite.h"       // for CanvasLayerComposite
 #include "ColorLayerComposite.h"        // for ColorLayerComposite
 #include "Composer2D.h"                 // for Composer2D
 #include "CompositableHost.h"           // for CompositableHost
 #include "ContainerLayerComposite.h"    // for ContainerLayerComposite, etc
 #include "FPSCounter.h"                 // for FPSState, FPSCounter
+#include "PaintCounter.h"               // For PaintCounter
 #include "FrameMetrics.h"               // for FrameMetrics
 #include "GeckoProfiler.h"              // for profiler_set_frame_number, etc
 #include "ImageLayerComposite.h"        // for ImageLayerComposite
 #include "Layers.h"                     // for Layer, ContainerLayer, etc
 #include "LayerScope.h"                 // for LayerScope Tool
 #include "protobuf/LayerScopePacket.pb.h" // for protobuf (LayerScope)
 #include "PaintedLayerComposite.h"      // for PaintedLayerComposite
 #include "TiledContentHost.h"
@@ -122,16 +123,18 @@ LayerManagerComposite::LayerManagerCompo
 , mDisabledApzWarning(false)
 , mCompositor(aCompositor)
 , mInTransaction(false)
 , mIsCompositorReady(false)
 , mDebugOverlayWantsNextFrame(false)
 , mGeometryChanged(true)
 , mLastFrameMissedHWC(false)
 , mWindowOverlayChanged(false)
+, mLastPaintTime(TimeDuration::Forever())
+, mRenderStartTime(TimeStamp::Now())
 {
   mTextRenderer = new TextRenderer(aCompositor);
   MOZ_ASSERT(aCompositor);
 }
 
 LayerManagerComposite::~LayerManagerComposite()
 {
   Destroy();
@@ -143,16 +146,17 @@ LayerManagerComposite::Destroy()
 {
   if (!mDestroyed) {
     mCompositor->GetWidget()->CleanupWindowEffects();
     if (mRoot) {
       RootLayer()->Destroy();
     }
     mRoot = nullptr;
     mClonedLayerTreeProperties = nullptr;
+    mPaintCounter = nullptr;
     mDestroyed = true;
   }
 }
 
 void
 LayerManagerComposite::UpdateRenderBounds(const IntRect& aRect)
 {
   mRenderBounds = aRect;
@@ -368,16 +372,17 @@ LayerManagerComposite::PostProcessLayers
 void
 LayerManagerComposite::EndTransaction(const TimeStamp& aTimeStamp,
                                       EndTransactionFlags aFlags)
 {
   NS_ASSERTION(mInTransaction, "Didn't call BeginTransaction?");
   NS_ASSERTION(!(aFlags & END_NO_COMPOSITE),
                "Shouldn't get END_NO_COMPOSITE here");
   mInTransaction = false;
+  mRenderStartTime = TimeStamp::Now();
 
   if (!mIsCompositorReady) {
     return;
   }
   mIsCompositorReady = false;
 
 #ifdef MOZ_LAYERS_HAVE_LOG
   MOZ_LAYERS_LOG(("  ----- (beginning paint)"));
@@ -554,32 +559,48 @@ LayerManagerComposite::RootLayer() const
 #endif
 
 void
 LayerManagerComposite::InvalidateDebugOverlay(nsIntRegion& aInvalidRegion, const IntRect& aBounds)
 {
   bool drawFps = gfxPrefs::LayersDrawFPS();
   bool drawFrameCounter = gfxPrefs::DrawFrameCounter();
   bool drawFrameColorBars = gfxPrefs::CompositorDrawColorBars();
+  bool drawPaintTimes = gfxPrefs::AlwaysPaint();
 
   if (drawFps || drawFrameCounter) {
     aInvalidRegion.Or(aInvalidRegion, nsIntRect(0, 0, 256, 256));
   }
   if (drawFrameColorBars) {
     aInvalidRegion.Or(aInvalidRegion, nsIntRect(0, 0, 10, aBounds.height));
   }
+  if (drawPaintTimes) {
+    aInvalidRegion.Or(aInvalidRegion, nsIntRect(PaintCounter::GetPaintRect()));
+  }
+}
+
+void
+LayerManagerComposite::DrawPaintTimes(Compositor* aCompositor)
+{
+  if (!mPaintCounter) {
+    mPaintCounter = new PaintCounter();
+  }
+
+  TimeDuration compositeTime = TimeStamp::Now() - mRenderStartTime;
+  mPaintCounter->Draw(aCompositor, mLastPaintTime, compositeTime);
 }
 
 static uint16_t sFrameCount = 0;
 void
 LayerManagerComposite::RenderDebugOverlay(const IntRect& aBounds)
 {
   bool drawFps = gfxPrefs::LayersDrawFPS();
   bool drawFrameCounter = gfxPrefs::DrawFrameCounter();
   bool drawFrameColorBars = gfxPrefs::CompositorDrawColorBars();
+  bool drawPaintTimes = gfxPrefs::AlwaysPaint();
 
   TimeStamp now = TimeStamp::Now();
 
   if (drawFps) {
     if (!mFPS) {
       mFPS = MakeUnique<FPSState>();
     }
 
@@ -709,16 +730,20 @@ LayerManagerComposite::RenderDebugOverla
 
   }
 #endif
 
   if (drawFrameColorBars || drawFrameCounter) {
     // We intentionally overflow at 2^16.
     sFrameCount++;
   }
+
+  if (drawPaintTimes) {
+    DrawPaintTimes(mCompositor);
+  }
 }
 
 RefPtr<CompositingRenderTarget>
 LayerManagerComposite::PushGroupForLayerEffects()
 {
   // This is currently true, so just making sure that any new use of this
   // method is flagged for investigation
   MOZ_ASSERT(gfxPrefs::LayersEffectInvert() ||
--- a/gfx/layers/composite/LayerManagerComposite.h
+++ b/gfx/layers/composite/LayerManagerComposite.h
@@ -57,16 +57,17 @@ struct EffectChain;
 class ImageLayer;
 class ImageLayerComposite;
 class LayerComposite;
 class RefLayerComposite;
 class PaintedLayerComposite;
 class TextRenderer;
 class CompositingRenderTarget;
 struct FPSState;
+class PaintCounter;
 
 static const int kVisualWarningDuration = 150; // ms
 
 class LayerManagerComposite final : public LayerManager
 {
   typedef mozilla::gfx::DrawTarget DrawTarget;
   typedef mozilla::gfx::IntSize IntSize;
   typedef mozilla::gfx::SurfaceFormat SurfaceFormat;
@@ -326,16 +327,18 @@ public:
 
   // Indicate that we need to composite even if nothing in our layers has
   // changed, so that the widget can draw something different in its window
   // overlay.
   void SetWindowOverlayChanged() { mWindowOverlayChanged = true; }
 
   void ForcePresent() { mCompositor->ForcePresent(); }
 
+  void SetPaintTime(const TimeDuration& aPaintTime) { mLastPaintTime = aPaintTime; }
+
 private:
   /** Region we're clipping our current drawing to. */
   nsIntRegion mClippingRegion;
   gfx::IntRect mRenderBounds;
 
   /** Current root layer. */
   LayerComposite* RootLayer() const;
 
@@ -348,16 +351,21 @@ private:
    * Render the current layer tree to the active target.
    */
   void Render(const nsIntRegion& aInvalidRegion, const nsIntRegion& aOpaqueRegion);
 #if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
   void RenderToPresentationSurface();
 #endif
 
   /**
+   * Render paint and composite times above the frame.
+   */
+  void DrawPaintTimes(Compositor* aCompositor);
+
+  /**
    * We need to know our invalid region before we're ready to render.
    */
   void InvalidateDebugOverlay(nsIntRegion& aInvalidRegion, const gfx::IntRect& aBounds);
 
   /**
    * Render debug overlays such as the FPS/FrameCounter above the frame.
    */
   void RenderDebugOverlay(const gfx::IntRect& aBounds);
@@ -404,16 +412,19 @@ private:
   RefPtr<TextRenderer> mTextRenderer;
   bool mGeometryChanged;
 
   // Testing property. If hardware composer is supported, this will return
   // true if the last frame was deemed 'too complicated' to be rendered.
   bool mLastFrameMissedHWC;
 
   bool mWindowOverlayChanged;
+  RefPtr<PaintCounter> mPaintCounter;
+  TimeDuration mLastPaintTime;
+  TimeStamp mRenderStartTime;
 };
 
 /**
  * Composite layers are for use with OMTC on the compositor thread only. There
  * must be corresponding Basic layers on the content thread. For composite
  * layers, the layer manager only maintains the layer tree, all rendering is
  * done by a Compositor (see Compositor.h). As such, composite layers are
  * platform-independent and can be used on any platform for which there is a
new file mode 100644
--- /dev/null
+++ b/gfx/layers/composite/PaintCounter.cpp
@@ -0,0 +1,84 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * 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 "mozilla/gfx/Point.h"          // for IntSize, Point
+#include "mozilla/gfx/Rect.h"           // for Rect
+#include "mozilla/gfx/Types.h"          // for Color, SurfaceFormat
+#include "mozilla/layers/Compositor.h"  // for Compositor
+#include "mozilla/layers/CompositorTypes.h"
+#include "mozilla/layers/Effects.h"     // for Effect, EffectChain, etc
+#include "mozilla/TimeStamp.h"          // for TimeStamp, TimeDuration
+#include "mozilla/Sprintf.h"
+
+#include "SkColor.h"
+#include "mozilla/gfx/HelpersSkia.h"
+#include "skia/include/core/SkBitmapDevice.h"
+#include "PaintCounter.h"
+
+namespace mozilla {
+namespace layers {
+
+using namespace mozilla::gfx;
+
+// Positioned below the chrome UI
+IntRect PaintCounter::mRect = IntRect(0, 175, 300, 60);
+
+PaintCounter::PaintCounter()
+{
+  mFormat = SurfaceFormat::B8G8R8A8;
+  mSurface = Factory::CreateDataSourceSurface(mRect.Size(), mFormat);
+  mStride = mSurface->Stride();
+
+  SkBitmap bitmap;
+  bitmap.setInfo(MakeSkiaImageInfo(mRect.Size(), mFormat), mStride);
+  bitmap.setPixels(mSurface->GetData());
+  bitmap.eraseColor(SK_ColorWHITE);
+
+  mCanvas.adopt(new SkCanvas(bitmap));
+}
+
+PaintCounter::~PaintCounter()
+{
+  mSurface = nullptr;
+  mTextureSource = nullptr;
+  mTexturedEffect = nullptr;
+}
+
+void
+PaintCounter::Draw(Compositor* aCompositor, TimeDuration aPaintTime, TimeDuration aCompositeTime) {
+  const int buffer_size = 48;
+  char buffer[buffer_size];
+  snprintf(buffer, buffer_size, "P: %.2f  C: %.2f",
+           aPaintTime.ToMilliseconds(),
+           aCompositeTime.ToMilliseconds());
+
+  SkPaint paint;
+  paint.setTextSize(32);
+  paint.setColor(SkColorSetRGB(0, 255, 0));
+  paint.setAntiAlias(true);
+
+  mCanvas->clear(SK_ColorTRANSPARENT);
+  mCanvas->drawText(buffer, strlen(buffer), 10, 30, paint);
+  mCanvas->flush();
+
+  if (!mTextureSource) {
+    mTextureSource = aCompositor->CreateDataTextureSource();
+    mTexturedEffect = CreateTexturedEffect(mFormat, mTextureSource,
+                                           SamplingFilter::POINT, true);
+    mTexturedEffect->mTextureCoords = Rect(0, 0, 1.0f, 1.0f);
+  }
+
+  mTextureSource->Update(mSurface);
+
+  EffectChain effectChain;
+  effectChain.mPrimaryEffect = mTexturedEffect;
+
+  gfx::Matrix4x4 identity;
+  Rect rect(mRect.x, mRect.y, mRect.width, mRect.height);
+  aCompositor->DrawQuad(rect, mRect, effectChain, 1.0, identity);
+}
+
+} // end namespace layers
+} // end namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/layers/composite/PaintCounter.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * 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_PaintCounter_h_
+#define mozilla_layers_PaintCounter_h_
+
+#include <map>                          // for std::map
+#include "mozilla/RefPtr.h"             // for already_AddRefed, RefCounted
+#include "mozilla/TimeStamp.h"          // for TimeStamp, TimeDuration
+#include "skia/include/core/SkCanvas.h"
+#include "mozilla/gfx/HelpersSkia.h"
+
+namespace mozilla {
+namespace layers {
+
+class Compositor;
+
+using namespace mozilla::gfx;
+using namespace mozilla::gl;
+
+// Keeps track and paints how long a full invalidation paint takes to rasterize
+// and composite.
+class PaintCounter {
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(PaintCounter)
+
+  PaintCounter();
+  void Draw(Compositor* aCompositor, TimeDuration aPaintTime, TimeDuration aCompositeTime);
+  static IntRect GetPaintRect() { return PaintCounter::mRect; }
+
+private:
+  virtual ~PaintCounter();
+
+  SurfaceFormat mFormat;
+  RefPtrSkia<SkCanvas> mCanvas;
+  IntSize mSize;
+  int mStride;
+
+  RefPtr<DataSourceSurface> mSurface;
+  RefPtr<DataTextureSource> mTextureSource;
+  RefPtr<TexturedEffect> mTexturedEffect;
+  static IntRect mRect;
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif // mozilla_layers_opengl_PaintCounter_h_
--- a/gfx/layers/ipc/CompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/CompositorBridgeParent.cpp
@@ -610,16 +610,17 @@ CompositorBridgeParent::CompositorBridge
   , mPauseCompositionMonitor("PauseCompositionMonitor")
   , mResumeCompositionMonitor("ResumeCompositionMonitor")
   , mResetCompositorMonitor("ResetCompositorMonitor")
   , mRootLayerTreeID(0)
   , mOverrideComposeReadiness(false)
   , mForceCompositionTask(nullptr)
   , mCompositorThreadHolder(CompositorThreadHolder::GetSingleton())
   , mCompositorScheduler(nullptr)
+  , mPaintTime(TimeDuration::Forever())
 #if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
   , mLastPluginUpdateLayerTreeId(0)
   , mDeferPluginWindows(false)
   , mPluginWindowsHidden(false)
 #endif
 {
   // Always run destructor on the main thread
   MOZ_ASSERT(NS_IsMainThread());
@@ -1074,16 +1075,28 @@ CompositorBridgeParent::ScheduleTask(alr
   if (time == 0) {
     MessageLoop::current()->PostTask(Move(task));
   } else {
     MessageLoop::current()->PostDelayedTask(Move(task), time);
   }
 }
 
 void
+CompositorBridgeParent::UpdatePaintTime(LayerTransactionParent* aLayerTree,
+                                        const TimeDuration& aPaintTime)
+{
+  // We get a lot of paint timings for things with empty transactions.
+  if (!mLayerManager || aPaintTime.ToMilliseconds() < 1.0) {
+    return;
+  }
+
+  mLayerManager->SetPaintTime(aPaintTime);
+}
+
+void
 CompositorBridgeParent::NotifyShadowTreeTransaction(uint64_t aId, bool aIsFirstPaint,
     bool aScheduleComposite, uint32_t aPaintSequenceNumber,
     bool aIsRepeatTransaction, bool aHitTestUpdate)
 {
   if (!aIsRepeatTransaction &&
       mLayerManager &&
       mLayerManager->GetRoot()) {
     // Process plugin data here to give time for them to update before the next
@@ -2125,16 +2138,29 @@ public:
   }
   bool DeallocPCompositorWidgetParent(PCompositorWidgetParent* aActor) override {
     // Not allowed.
     return false;
   }
 
   virtual CompositorBridgeParentIPCAllocator* AsCompositorBridgeParentIPCAllocator() override { return this; }
 
+  virtual void UpdatePaintTime(LayerTransactionParent* aLayerTree, const TimeDuration& aPaintTime) override {
+    uint64_t id = aLayerTree->GetId();
+    MOZ_ASSERT(id != 0);
+
+    CompositorBridgeParent::LayerTreeState* state =
+      CompositorBridgeParent::GetIndirectShadowTree(id);
+    if (!state || !state->mParent) {
+      return;
+    }
+
+    state->mParent->UpdatePaintTime(aLayerTree, aPaintTime);
+  }
+
 protected:
   void OnChannelConnected(int32_t pid) override {
     mCompositorThreadHolder = CompositorThreadHolder::GetSingleton();
   }
 private:
   // Private destructor, to discourage deletion outside of Release():
   virtual ~CrossProcessCompositorBridgeParent();
 
--- a/gfx/layers/ipc/CompositorBridgeParent.h
+++ b/gfx/layers/ipc/CompositorBridgeParent.h
@@ -367,16 +367,19 @@ public:
   bool ScheduleResumeOnCompositorThread();
   bool ScheduleResumeOnCompositorThread(int width, int height);
 
   virtual void ScheduleComposition();
   void NotifyShadowTreeTransaction(uint64_t aId, bool aIsFirstPaint,
       bool aScheduleComposite, uint32_t aPaintSequenceNumber,
       bool aIsRepeatTransaction, bool aHitTestUpdate);
 
+  void UpdatePaintTime(LayerTransactionParent* aLayerTree,
+                       const TimeDuration& aPaintTime) override;
+
   /**
    * Check rotation info and schedule a rendering task if needed.
    * Only can be called from compositor thread.
    */
   void ScheduleRotationOnCompositorThread(const TargetConfig& aTargetConfig, bool aIsFirstPaint);
 
   /**
    * Returns the unique layer tree identifier that corresponds to the root
@@ -625,16 +628,18 @@ protected:
 
   RefPtr<CompositorThreadHolder> mCompositorThreadHolder;
   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;
 
+  TimeDuration mPaintTime;
+
 #if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
   // cached plugin data used to reduce the number of updates we request.
   uint64_t mLastPluginUpdateLayerTreeId;
   nsIntPoint mPluginsLayerOffset;
   nsIntRegion mPluginsLayerVisibleRegion;
   nsTArray<PluginWindowData> mCachedPluginData;
   // Time until which we will block composition to wait for plugin updates.
   TimeStamp mWaitForPluginsUntil;
--- a/gfx/layers/ipc/LayerTransactionParent.cpp
+++ b/gfx/layers/ipc/LayerTransactionParent.cpp
@@ -224,16 +224,24 @@ public:
     }
   }
 private:
   LayerTransactionParent* mLayerTransaction;
   InfallibleTArray<OpDestroy>* mActorsToDestroy;
 };
 
 bool
+LayerTransactionParent::RecvPaintTime(const uint64_t& aTransactionId,
+                                      const TimeDuration& aPaintTime)
+{
+  mShadowLayersManager->UpdatePaintTime(this, aPaintTime);
+  return true;
+}
+
+bool
 LayerTransactionParent::RecvUpdate(InfallibleTArray<Edit>&& cset,
                                    InfallibleTArray<OpDestroy>&& aToDestroy,
                                    const uint64_t& aFwdTransactionId,
                                    const uint64_t& aTransactionId,
                                    const TargetConfig& targetConfig,
                                    PluginsArray&& aPlugins,
                                    const bool& isFirstPaint,
                                    const bool& scheduleComposite,
--- a/gfx/layers/ipc/LayerTransactionParent.h
+++ b/gfx/layers/ipc/LayerTransactionParent.h
@@ -104,16 +104,19 @@ public:
     mPendingCompositorUpdates--;
   }
 
 protected:
   virtual bool RecvSyncWithCompositor() override { return true; }
 
   virtual bool RecvShutdown() override;
 
+  virtual bool RecvPaintTime(const uint64_t& aTransactionId,
+                             const TimeDuration& aPaintTime) override;
+
   virtual bool RecvUpdate(EditArray&& cset,
                           OpDestroyArray&& aToDestroy,
                           const uint64_t& aFwdTransactionId,
                           const uint64_t& aTransactionId,
                           const TargetConfig& targetConfig,
                           PluginsArray&& aPlugins,
                           const bool& isFirstPaint,
                           const bool& scheduleComposite,
--- a/gfx/layers/ipc/PLayerTransaction.ipdl
+++ b/gfx/layers/ipc/PLayerTransaction.ipdl
@@ -53,16 +53,18 @@ parent:
               uint64_t fwdTransactionId,
               uint64_t id, TargetConfig targetConfig,
               PluginWindowData[] plugins, bool isFirstPaint,
               bool scheduleComposite, uint32_t paintSequenceNumber,
               bool isRepeatTransaction, TimeStamp transactionStart,
               int32_t paintSyncId)
     returns (EditReply[] reply);
 
+  async PaintTime(uint64_t id, TimeDuration paintTime);
+
   // We don't need to send a sync transaction if
   // no transaction operate require a swap.
   async UpdateNoSwap(Edit[] cset, OpDestroy[] toDestroy,
                      uint64_t fwdTransactionId,
                      uint64_t id, TargetConfig targetConfig,
                      PluginWindowData[] plugins, bool isFirstPaint,
                      bool scheduleComposite, uint32_t paintSequenceNumber,
                      bool isRepeatTransaction, TimeStamp transactionStart,
--- a/gfx/layers/ipc/ShadowLayers.cpp
+++ b/gfx/layers/ipc/ShadowLayers.cpp
@@ -584,16 +584,25 @@ ShadowLayerForwarder::StorePluginWidgetC
     const nsIWidget::Configuration& configuration = aConfigurations[idx];
     mPluginWindowData.AppendElement(PluginWindowData(configuration.mWindowID,
                                                      configuration.mClipRegion,
                                                      configuration.mBounds,
                                                      configuration.mVisible));
   }
 }
 
+void
+ShadowLayerForwarder::SendPaintTime(uint64_t aId, TimeDuration aPaintTime)
+{
+  if (!HasShadowManager() || !mShadowManager->IPCOpen() ||
+      !mShadowManager->SendPaintTime(aId, aPaintTime)) {
+    NS_WARNING("Could not send paint times over IPC");
+  }
+}
+
 bool
 ShadowLayerForwarder::EndTransaction(InfallibleTArray<EditReply>* aReplies,
                                      const nsIntRegion& aRegionToClear,
                                      uint64_t aId,
                                      bool aScheduleComposite,
                                      uint32_t aPaintSequenceNumber,
                                      bool aIsRepeatTransaction,
                                      const mozilla::TimeStamp& aTransactionStart,
--- a/gfx/layers/ipc/ShadowLayers.h
+++ b/gfx/layers/ipc/ShadowLayers.h
@@ -261,16 +261,21 @@ public:
                                          TextureClient* aClientOnWhite) override;
 #ifdef MOZ_WIDGET_GONK
   virtual void UseOverlaySource(CompositableClient* aCompositable,
                                 const OverlaySource& aOverlay,
                                 const nsIntRect& aPictureRect) override;
 #endif
 
   /**
+   * Used for debugging to tell the compositor how long this frame took to paint.
+   */
+  void SendPaintTime(uint64_t aId, TimeDuration aPaintTime);
+
+  /**
    * End the current transaction and forward it to LayerManagerComposite.
    * |aReplies| are directions from the LayerManagerComposite to the
    * caller of EndTransaction().
    */
   bool EndTransaction(InfallibleTArray<EditReply>* aReplies,
                       const nsIntRegion& aRegionToClear,
                       uint64_t aId,
                       bool aScheduleComposite,
--- a/gfx/layers/ipc/ShadowLayersManager.h
+++ b/gfx/layers/ipc/ShadowLayersManager.h
@@ -41,14 +41,15 @@ public:
     virtual void ApplyAsyncProperties(LayerTransactionParent* aLayerTree) = 0;
     virtual void FlushApzRepaints(const LayerTransactionParent* aLayerTree) = 0;
     virtual void GetAPZTestData(const LayerTransactionParent* aLayerTree,
                                 APZTestData* aOutData) { }
     virtual void SetConfirmedTargetAPZC(const LayerTransactionParent* aLayerTree,
                                         const uint64_t& aInputBlockId,
                                         const nsTArray<ScrollableLayerGuid>& aTargets) = 0;
     virtual CompositorBridgeParentIPCAllocator* AsCompositorBridgeParentIPCAllocator() { return nullptr; }
+    virtual void UpdatePaintTime(LayerTransactionParent* aLayerTree, const TimeDuration& aPaintTime) {}
 };
 
 } // namespace layers
 } // namespace mozilla
 
 #endif // mozilla_layers_ShadowLayersManager_h
--- a/gfx/layers/moz.build
+++ b/gfx/layers/moz.build
@@ -329,16 +329,17 @@ UNIFIED_SOURCES += [
     'composite/CompositableHost.cpp',
     'composite/ContainerLayerComposite.cpp',
     'composite/ContentHost.cpp',
     'composite/FPSCounter.cpp',
     'composite/FrameUniformityData.cpp',
     'composite/ImageHost.cpp',
     'composite/ImageLayerComposite.cpp',
     'composite/LayerManagerComposite.cpp',
+    'composite/PaintCounter.cpp',
     'composite/PaintedLayerComposite.cpp',
     'composite/TextRenderer.cpp',
     'composite/TextureHost.cpp',
     'composite/TiledContentHost.cpp',
     'Compositor.cpp',
     'CopyableCanvasLayer.cpp',
     'Effects.cpp',
     'FrameMetrics.cpp',
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -381,16 +381,17 @@ private:
   DECL_GFX_PREF(Live, "gfx.partialpresent.force",              PartialPresent, int32_t, 0);
   DECL_GFX_PREF(Live, "gfx.perf-warnings.enabled",             PerfWarnings, bool, false);
   DECL_GFX_PREF(Live, "gfx.SurfaceTexture.detach.enabled",     SurfaceTextureDetachEnabled, bool, true);
   DECL_GFX_PREF(Live, "gfx.testing.device-reset",              DeviceResetForTesting, int32_t, 0);
   DECL_GFX_PREF(Live, "gfx.testing.device-fail",               DeviceFailForTesting, bool, false);
   DECL_GFX_PREF(Live, "gfx.ycbcr.accurate-conversion",         YCbCrAccurateConversion, bool, false);
 
   DECL_GFX_PREF(Live, "gfx.content.use-native-pushlayer",      UseNativePushLayer, bool, false);
+  DECL_GFX_PREF(Live, "gfx.content.always-paint",              AlwaysPaint, bool, false);
 
   // Disable surface sharing due to issues with compatible FBConfigs on
   // NVIDIA drivers as described in bug 1193015.
   DECL_GFX_PREF(Live, "gfx.use-glx-texture-from-pixmap",       UseGLXTextureFromPixmap, bool, false);
 
   DECL_GFX_PREF(Once, "gfx.use-iosurface-textures",            UseIOSurfaceTextures, bool, false);
 
   // These times should be in milliseconds
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -1760,16 +1760,21 @@ already_AddRefed<LayerManager> nsDisplay
 
   if (doBeginTransaction) {
     if (aCtx) {
       layerManager->BeginTransactionWithTarget(aCtx->ThebesContext());
     } else {
       layerManager->BeginTransaction();
     }
   }
+
+  if (XRE_IsContentProcess() && gfxPrefs::AlwaysPaint()) {
+    FrameLayerBuilder::InvalidateAllLayers(layerManager);
+  }
+
   if (widgetTransaction) {
     layerBuilder->DidBeginRetainedLayerTransaction(layerManager);
   }
 
   nsIFrame* frame = aBuilder->RootReferenceFrame();
   nsPresContext* presContext = frame->PresContext();
   nsIPresShell* presShell = presContext->GetPresShell();
   nsRootPresContext* rootPresContext = presContext->GetRootPresContext();
--- a/layout/base/nsRefreshDriver.cpp
+++ b/layout/base/nsRefreshDriver.cpp
@@ -1923,16 +1923,20 @@ nsRefreshDriver::Tick(int64_t aNowEpoch,
   while (iter.HasMore()) {
     nsAPostRefreshObserver* observer = iter.GetNext();
     observer->DidRefresh();
   }
 
   ConfigureHighPrecision();
 
   NS_ASSERTION(mInRefresh, "Still in refresh");
+
+  if (mPresContext->IsRoot() && XRE_IsContentProcess() && gfxPrefs::AlwaysPaint()) {
+    ScheduleViewManagerFlush();
+  }
 }
 
 void
 nsRefreshDriver::BeginRefreshingImages(RequestTable& aEntries,
                                        mozilla::TimeStamp aDesired)
 {
   for (auto iter = aEntries.Iter(); !iter.Done(); iter.Next()) {
     auto req = static_cast<imgIRequest*>(iter.Get()->GetKey());
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -4563,16 +4563,18 @@ pref("layers.offmainthreadcomposition.lo
 
 pref("layers.bufferrotation.enabled", true);
 
 pref("layers.componentalpha.enabled", true);
 
 // Use the DT-backend implemented PushLayer
 pref("gfx.content.use-native-pushlayer", false);
 
+pref("gfx.content.always-paint", false);
+
 #ifdef ANDROID
 pref("gfx.apitrace.enabled",false);
 #endif
 
 #ifdef MOZ_X11
 pref("gfx.content.use-native-pushlayer", true);
 #ifdef MOZ_WIDGET_GTK
 pref("gfx.xrender.enabled",false);