Bug 1156981 - Split CompositorParent's scheduling of composition to CompositorScheduler r=mchang
authorSotaro Ikeda <sikeda@mozilla.com>
Thu, 30 Apr 2015 11:35:13 -0700
changeset 273260 6deee758b43ce4af8e38891b871fa0d6479a306f
parent 273259 b09fdaa6314a7598097ef6a665bc36c40492c8be
child 273261 4eb6bc8c98c864f4e3011b72aafcadb21647aa4e
push id863
push userraliiev@mozilla.com
push dateMon, 03 Aug 2015 13:22:43 +0000
treeherdermozilla-release@f6321b14228d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmchang
bugs1156981
milestone40.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 1156981 - Split CompositorParent's scheduling of composition to CompositorScheduler r=mchang
gfx/layers/ipc/CompositorParent.cpp
gfx/layers/ipc/CompositorParent.h
widget/gonk/GeckoTouchDispatcher.cpp
widget/gonk/GeckoTouchDispatcher.h
--- a/gfx/layers/ipc/CompositorParent.cpp
+++ b/gfx/layers/ipc/CompositorParent.cpp
@@ -213,53 +213,236 @@ static Thread* CompositorThread() {
   return sCompositorThreadHolder ? sCompositorThreadHolder->GetCompositorThread() : nullptr;
 }
 
 static void SetThreadPriority()
 {
   hal::SetCurrentThreadPriority(hal::THREAD_PRIORITY_COMPOSITOR);
 }
 
-CompositorVsyncObserver::CompositorVsyncObserver(CompositorParent* aCompositorParent, nsIWidget* aWidget)
-  : mNeedsComposite(false)
+CompositorScheduler::CompositorScheduler(CompositorParent* aCompositorParent)
+  : mCompositorParent(aCompositorParent)
+  , mCurrentCompositeTask(nullptr)
+{
+}
+
+CompositorScheduler::~CompositorScheduler()
+{
+  MOZ_ASSERT(!mCompositorParent);
+}
+
+void
+CompositorScheduler::CancelCurrentCompositeTask()
+{
+  if (mCurrentCompositeTask) {
+    mCurrentCompositeTask->Cancel();
+    mCurrentCompositeTask = nullptr;
+  }
+}
+
+void
+CompositorScheduler::ScheduleTask(CancelableTask* aTask, int aTime)
+{
+  MOZ_ASSERT(CompositorParent::CompositorLoop());
+  MOZ_ASSERT(aTime >= 0);
+  CompositorParent::CompositorLoop()->PostDelayedTask(FROM_HERE, aTask, aTime);
+}
+
+void
+CompositorScheduler::ResumeComposition()
+{
+  mLastCompose = TimeStamp::Now();
+  ComposeToTarget(nullptr);
+}
+
+void
+CompositorScheduler::ForceComposeToTarget(gfx::DrawTarget* aTarget, const nsIntRect* aRect)
+{
+  mLastCompose = TimeStamp::Now();
+  ComposeToTarget(aTarget, aRect);
+}
+
+void
+CompositorScheduler::ComposeToTarget(gfx::DrawTarget* aTarget, const nsIntRect* aRect)
+{
+  MOZ_ASSERT(CompositorParent::IsInCompositorThread());
+  MOZ_ASSERT(mCompositorParent);
+  mCompositorParent->CompositeToTarget(aTarget, aRect);
+}
+
+void
+CompositorScheduler::Destroy()
+{
+  MOZ_ASSERT(CompositorParent::IsInCompositorThread());
+  CancelCurrentCompositeTask();
+  mCompositorParent = nullptr;
+}
+
+CompositorSoftwareTimerScheduler::CompositorSoftwareTimerScheduler(CompositorParent* aCompositorParent)
+  : CompositorScheduler(aCompositorParent)
+{
+}
+
+CompositorSoftwareTimerScheduler::~CompositorSoftwareTimerScheduler()
+{
+  MOZ_ASSERT(!mCurrentCompositeTask);
+}
+
+// Used when layout.frame_rate is -1. Needs to be kept in sync with
+// DEFAULT_FRAME_RATE in nsRefreshDriver.cpp.
+static const int32_t kDefaultFrameRate = 60;
+
+static int32_t
+CalculateCompositionFrameRate()
+{
+  int32_t compositionFrameRatePref = gfxPrefs::LayersCompositionFrameRate();
+  if (compositionFrameRatePref < 0) {
+    // Use the same frame rate for composition as for layout.
+    int32_t layoutFrameRatePref = gfxPrefs::LayoutFrameRate();
+    if (layoutFrameRatePref < 0) {
+      // TODO: The main thread frame scheduling code consults the actual
+      // monitor refresh rate in this case. We should do the same.
+      return kDefaultFrameRate;
+    }
+    return layoutFrameRatePref;
+  }
+  return compositionFrameRatePref;
+}
+
+void
+CompositorSoftwareTimerScheduler::ScheduleComposition()
+{
+  if (mCurrentCompositeTask) {
+    return;
+  }
+
+  bool initialComposition = mLastCompose.IsNull();
+  TimeDuration delta;
+  if (!initialComposition) {
+    delta = TimeStamp::Now() - mLastCompose;
+  }
+
+  int32_t rate = CalculateCompositionFrameRate();
+
+  // If rate == 0 (ASAP mode), minFrameDelta must be 0 so there's no delay.
+  TimeDuration minFrameDelta = TimeDuration::FromMilliseconds(
+    rate == 0 ? 0.0 : std::max(0.0, 1000.0 / rate));
+
+  mCurrentCompositeTask = NewRunnableMethod(this,
+                                            &CompositorSoftwareTimerScheduler::CallComposite);
+
+  if (!initialComposition && delta < minFrameDelta) {
+    TimeDuration delay = minFrameDelta - delta;
+#ifdef COMPOSITOR_PERFORMANCE_WARNING
+    mExpectedComposeStartTime = TimeStamp::Now() + delay;
+#endif
+    ScheduleTask(mCurrentCompositeTask, delay.ToMilliseconds());
+  } else {
+#ifdef COMPOSITOR_PERFORMANCE_WARNING
+    mExpectedComposeStartTime = TimeStamp::Now();
+#endif
+    ScheduleTask(mCurrentCompositeTask, 0);
+  }
+}
+
+bool
+CompositorSoftwareTimerScheduler::NeedsComposite()
+{
+  return mCurrentCompositeTask ? true : false;
+}
+
+void
+CompositorSoftwareTimerScheduler::CallComposite()
+{
+  Composite(TimeStamp::Now());
+}
+
+void
+CompositorSoftwareTimerScheduler::Composite(TimeStamp aTimestamp)
+{
+  mCurrentCompositeTask = nullptr;
+  mLastCompose = aTimestamp;
+  ComposeToTarget(nullptr);
+}
+
+CompositorVsyncScheduler::Observer::Observer(CompositorVsyncScheduler* aOwner)
+  : mMutex("CompositorVsyncScheduler.Observer.Mutex")
+  , mOwner(aOwner)
+{
+}
+
+CompositorVsyncScheduler::Observer::~Observer()
+{
+  MOZ_ASSERT(!mOwner);
+}
+
+bool
+CompositorVsyncScheduler::Observer::NotifyVsync(TimeStamp aVsyncTimestamp)
+{
+  MutexAutoLock lock(mMutex);
+  if (!mOwner) {
+    return false;
+  }
+  return mOwner->NotifyVsync(aVsyncTimestamp);
+}
+
+void
+CompositorVsyncScheduler::Observer::Destroy()
+{
+  MutexAutoLock lock(mMutex);
+  mOwner = nullptr;
+}
+
+CompositorVsyncScheduler::CompositorVsyncScheduler(CompositorParent* aCompositorParent, nsIWidget* aWidget)
+  : CompositorScheduler(aCompositorParent)
+  , mNeedsComposite(false)
   , mIsObservingVsync(false)
   , mVsyncNotificationsSkipped(0)
   , mCompositorParent(aCompositorParent)
   , mCurrentCompositeTaskMonitor("CurrentCompositeTaskMonitor")
-  , mCurrentCompositeTask(nullptr)
   , mSetNeedsCompositeMonitor("SetNeedsCompositeMonitor")
   , mSetNeedsCompositeTask(nullptr)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aWidget != nullptr);
+  mVsyncObserver = new Observer(this);
   mCompositorVsyncDispatcher = aWidget->GetCompositorVsyncDispatcher();
 #ifdef MOZ_WIDGET_GONK
-  GeckoTouchDispatcher::GetInstance()->SetCompositorVsyncObserver(this);
+  GeckoTouchDispatcher::GetInstance()->SetCompositorVsyncScheduler(this);
 #endif
 }
 
-CompositorVsyncObserver::~CompositorVsyncObserver()
+CompositorVsyncScheduler::~CompositorVsyncScheduler()
 {
   MOZ_ASSERT(!mIsObservingVsync);
+  MOZ_ASSERT(!mVsyncObserver);
   // The CompositorVsyncDispatcher is cleaned up before this in the nsBaseWidget, which stops vsync listeners
   mCompositorParent = nullptr;
   mCompositorVsyncDispatcher = nullptr;
 }
 
 void
-CompositorVsyncObserver::Destroy()
+CompositorVsyncScheduler::Destroy()
 {
   MOZ_ASSERT(CompositorParent::IsInCompositorThread());
   UnobserveVsync();
-  CancelCurrentCompositeTask();
+  mVsyncObserver->Destroy();
+  mVsyncObserver = nullptr;
   CancelCurrentSetNeedsCompositeTask();
+  CompositorScheduler::Destroy();
 }
 
 void
-CompositorVsyncObserver::CancelCurrentSetNeedsCompositeTask()
+CompositorVsyncScheduler::ScheduleComposition()
+{
+  SetNeedsComposite(true);
+}
+
+void
+CompositorVsyncScheduler::CancelCurrentSetNeedsCompositeTask()
 {
   MOZ_ASSERT(CompositorParent::IsInCompositorThread());
   MonitorAutoLock lock(mSetNeedsCompositeMonitor);
   if (mSetNeedsCompositeTask) {
     mSetNeedsCompositeTask->Cancel();
     mSetNeedsCompositeTask = nullptr;
   }
   mNeedsComposite = false;
@@ -268,128 +451,131 @@ CompositorVsyncObserver::CancelCurrentSe
 /**
  * TODO Potential performance heuristics:
  * If a composite takes 17 ms, do we composite ASAP or wait until next vsync?
  * If a layer transaction comes after vsync, do we composite ASAP or wait until
  * next vsync?
  * How many skipped vsync events until we stop listening to vsync events?
  */
 void
-CompositorVsyncObserver::SetNeedsComposite(bool aNeedsComposite)
+CompositorVsyncScheduler::SetNeedsComposite(bool aNeedsComposite)
 {
   if (!CompositorParent::IsInCompositorThread()) {
     MonitorAutoLock lock(mSetNeedsCompositeMonitor);
     mSetNeedsCompositeTask = NewRunnableMethod(this,
-                                              &CompositorVsyncObserver::SetNeedsComposite,
+                                              &CompositorVsyncScheduler::SetNeedsComposite,
                                               aNeedsComposite);
-    MOZ_ASSERT(CompositorParent::CompositorLoop());
-    CompositorParent::CompositorLoop()->PostTask(FROM_HERE, mSetNeedsCompositeTask);
+    ScheduleTask(mSetNeedsCompositeTask, 0);
     return;
   } else {
     MonitorAutoLock lock(mSetNeedsCompositeMonitor);
     mSetNeedsCompositeTask = nullptr;
   }
 
   mNeedsComposite = aNeedsComposite;
   if (!mIsObservingVsync && mNeedsComposite) {
     ObserveVsync();
   }
 }
 
 bool
-CompositorVsyncObserver::NotifyVsync(TimeStamp aVsyncTimestamp)
+CompositorVsyncScheduler::NotifyVsync(TimeStamp aVsyncTimestamp)
 {
   // Called from the vsync dispatch thread
   MOZ_ASSERT(!CompositorParent::IsInCompositorThread());
   MOZ_ASSERT(!NS_IsMainThread());
 
   MonitorAutoLock lock(mCurrentCompositeTaskMonitor);
   if (mCurrentCompositeTask == nullptr) {
     mCurrentCompositeTask = NewRunnableMethod(this,
-                                              &CompositorVsyncObserver::Composite,
+                                              &CompositorVsyncScheduler::Composite,
                                               aVsyncTimestamp);
-    MOZ_ASSERT(CompositorParent::CompositorLoop());
-    CompositorParent::CompositorLoop()->PostTask(FROM_HERE, mCurrentCompositeTask);
+    ScheduleTask(mCurrentCompositeTask, 0);
   }
   return true;
 }
 
 void
-CompositorVsyncObserver::CancelCurrentCompositeTask()
+CompositorVsyncScheduler::CancelCurrentCompositeTask()
 {
   MOZ_ASSERT(CompositorParent::IsInCompositorThread() || NS_IsMainThread());
   MonitorAutoLock lock(mCurrentCompositeTaskMonitor);
-  if (mCurrentCompositeTask) {
-    mCurrentCompositeTask->Cancel();
-    mCurrentCompositeTask = nullptr;
-  }
+  CompositorScheduler::CancelCurrentCompositeTask();
 }
 
 void
-CompositorVsyncObserver::Composite(TimeStamp aVsyncTimestamp)
+CompositorVsyncScheduler::Composite(TimeStamp aVsyncTimestamp)
 {
   MOZ_ASSERT(CompositorParent::IsInCompositorThread());
   {
     MonitorAutoLock lock(mCurrentCompositeTaskMonitor);
     mCurrentCompositeTask = nullptr;
   }
 
   DispatchTouchEvents(aVsyncTimestamp);
 
-  if (mNeedsComposite && mCompositorParent) {
+  if (mNeedsComposite) {
     mNeedsComposite = false;
-    mCompositorParent->CompositeCallback(aVsyncTimestamp);
+    mLastCompose = aVsyncTimestamp;
+    ComposeToTarget(nullptr);
     mVsyncNotificationsSkipped = 0;
   } else if (mVsyncNotificationsSkipped++ > gfxPrefs::CompositorUnobserveCount()) {
     UnobserveVsync();
   }
 }
 
 void
-CompositorVsyncObserver::OnForceComposeToTarget()
+CompositorVsyncScheduler::OnForceComposeToTarget()
 {
   /**
    * bug 1138502 - There are cases such as during long-running window resizing events
    * where we receive many sync RecvFlushComposites. We also get vsync notifications which
    * will increment mVsyncNotificationsSkipped because a composite just occurred. After
    * enough vsyncs and RecvFlushComposites occurred, we will disable vsync. Then at the next
    * ScheduleComposite, we will enable vsync, then get a RecvFlushComposite, which will
    * force us to unobserve vsync again. On some platforms, enabling/disabling vsync is not
    * free and this oscillating behavior causes a performance hit. In order to avoid this problem,
    * we reset the mVsyncNotificationsSkipped counter to keep vsync enabled.
    */
   MOZ_ASSERT(CompositorParent::IsInCompositorThread());
   mVsyncNotificationsSkipped = 0;
 }
 
+void
+CompositorVsyncScheduler::ForceComposeToTarget(gfx::DrawTarget* aTarget, const nsIntRect* aRect)
+{
+  OnForceComposeToTarget();
+  CompositorScheduler::ForceComposeToTarget(aTarget, aRect);
+}
+
 bool
-CompositorVsyncObserver::NeedsComposite()
+CompositorVsyncScheduler::NeedsComposite()
 {
   MOZ_ASSERT(CompositorParent::IsInCompositorThread());
   return mNeedsComposite;
 }
 
 void
-CompositorVsyncObserver::ObserveVsync()
+CompositorVsyncScheduler::ObserveVsync()
 {
   MOZ_ASSERT(CompositorParent::IsInCompositorThread());
-  mCompositorVsyncDispatcher->SetCompositorVsyncObserver(this);
+  mCompositorVsyncDispatcher->SetCompositorVsyncObserver(mVsyncObserver);
   mIsObservingVsync = true;
 }
 
 void
-CompositorVsyncObserver::UnobserveVsync()
+CompositorVsyncScheduler::UnobserveVsync()
 {
   MOZ_ASSERT(CompositorParent::IsInCompositorThread());
   mCompositorVsyncDispatcher->SetCompositorVsyncObserver(nullptr);
   mIsObservingVsync = false;
 }
 
 void
-CompositorVsyncObserver::DispatchTouchEvents(TimeStamp aVsyncTimestamp)
+CompositorVsyncScheduler::DispatchTouchEvents(TimeStamp aVsyncTimestamp)
 {
 #ifdef MOZ_WIDGET_GONK
   GeckoTouchDispatcher::GetInstance()->NotifyVsync(aVsyncTimestamp);
 #endif
 }
 
 void CompositorParent::StartUp()
 {
@@ -437,28 +623,27 @@ UseVsyncComposition()
           && !IsInCompositorAsapMode()
           && !gfxPlatform::IsInLayoutAsapMode();
 }
 
 CompositorParent::CompositorParent(nsIWidget* aWidget,
                                    bool aUseExternalSurfaceSize,
                                    int aSurfaceWidth, int aSurfaceHeight)
   : mWidget(aWidget)
-  , mCurrentCompositeTask(nullptr)
   , mIsTesting(false)
   , mPendingTransaction(0)
   , mPaused(false)
   , mUseExternalSurfaceSize(aUseExternalSurfaceSize)
   , mEGLSurfaceSize(aSurfaceWidth, aSurfaceHeight)
   , mPauseCompositionMonitor("PauseCompositionMonitor")
   , mResumeCompositionMonitor("ResumeCompositionMonitor")
   , mOverrideComposeReadiness(false)
   , mForceCompositionTask(nullptr)
   , mCompositorThreadHolder(sCompositorThreadHolder)
-  , mCompositorVsyncObserver(nullptr)
+  , mCompositorScheduler(nullptr)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(CompositorThread(),
              "The compositor thread must be Initialized before instanciating a CompositorParent.");
   MOZ_COUNT_CTOR(CompositorParent);
   mCompositorID = 0;
   // FIXME: This holds on the the fact that right now the only thing that
   // can destroy this instance is initialized on the compositor thread after
@@ -477,17 +662,19 @@ CompositorParent::CompositorParent(nsIWi
   }
 
   if (gfxPrefs::AsyncPanZoomEnabled()) {
     mApzcTreeManager = new APZCTreeManager();
   }
 
   if (UseVsyncComposition()) {
     NS_WARNING("Enabling vsync compositor\n");
-    mCompositorVsyncObserver = new CompositorVsyncObserver(this, aWidget);
+    mCompositorScheduler = new CompositorVsyncScheduler(this, aWidget);
+  } else {
+    mCompositorScheduler = new CompositorSoftwareTimerScheduler(this);
   }
 
   gfxPlatform::GetPlatform()->ComputeTileSize();
 }
 
 bool
 CompositorParent::IsInCompositorThread()
 {
@@ -524,20 +711,18 @@ CompositorParent::Destroy()
   if (mApzcTreeManager) {
     mApzcTreeManager->ClearTree();
     mApzcTreeManager = nullptr;
   }
   { // scope lock
     MonitorAutoLock lock(*sIndirectLayerTreesLock);
     sIndirectLayerTrees.erase(mRootLayerTreeID);
   }
-  if (mCompositorVsyncObserver) {
-    mCompositorVsyncObserver->Destroy();
-    mCompositorVsyncObserver = nullptr;
-  }
+
+  mCompositorScheduler->Destroy();
 }
 
 void
 CompositorParent::ForceIsFirstPaint()
 {
   mCompositionManager->ForceIsFirstPaint();
 }
 
@@ -611,27 +796,21 @@ CompositorParent::RecvMakeSnapshot(const
   RefPtr<DrawTarget> target = GetDrawTargetForDescriptor(aInSnapshot, gfx::BackendType::CAIRO);
   ForceComposeToTarget(target, &aRect);
   return true;
 }
 
 bool
 CompositorParent::RecvFlushRendering()
 {
-  if (mCompositorVsyncObserver && mCompositorVsyncObserver->NeedsComposite()) {
-    mCompositorVsyncObserver->SetNeedsComposite(false);
-    CancelCurrentCompositeTask();
-    ForceComposeToTarget(nullptr);
-  } else if (mCurrentCompositeTask) {
-    // If we're waiting to do a composite, then cancel it
-    // and do it immediately instead.
+  if (mCompositorScheduler->NeedsComposite())
+  {
     CancelCurrentCompositeTask();
     ForceComposeToTarget(nullptr);
   }
-
   return true;
 }
 
 bool
 CompositorParent::RecvGetTileSize(int32_t* aWidth, int32_t* aHeight)
 {
   *aWidth = gfxPlatform::GetPlatform()->GetTileWidth();
   *aHeight = gfxPlatform::GetPlatform()->GetTileHeight();
@@ -734,40 +913,34 @@ CompositorParent::ResumeComposition()
     __android_log_print(ANDROID_LOG_INFO, "CompositorParent", "Unable to renew compositor surface; remaining in paused state");
 #endif
     lock.NotifyAll();
     return;
   }
 
   mPaused = false;
 
-  mLastCompose = TimeStamp::Now();
-  CompositeToTarget(nullptr);
+  mCompositorScheduler->ResumeComposition();
 
   // if anyone's waiting to make sure that composition really got resumed, tell them
   lock.NotifyAll();
 }
 
 void
 CompositorParent::ForceComposition()
 {
   // Cancel the orientation changed state to force composition
   mForceCompositionTask = nullptr;
   ScheduleRenderOnCompositorThread();
 }
 
 void
 CompositorParent::CancelCurrentCompositeTask()
 {
-  if (mCompositorVsyncObserver) {
-    mCompositorVsyncObserver->CancelCurrentCompositeTask();
-  } else if (mCurrentCompositeTask) {
-    mCurrentCompositeTask->Cancel();
-    mCurrentCompositeTask = nullptr;
-  }
+  mCompositorScheduler->CancelCurrentCompositeTask();
 }
 
 void
 CompositorParent::SetEGLSurfaceSize(int width, int height)
 {
   NS_ASSERTION(mUseExternalSurfaceSize, "Compositor created without UseExternalSurfaceSize provided");
   mEGLSurfaceSize.SizeTo(width, height);
   if (mCompositor) {
@@ -857,104 +1030,25 @@ CompositorParent::NotifyShadowTreeTransa
 
     mLayerManager->NotifyShadowTreeTransaction();
   }
   if (aScheduleComposite) {
     ScheduleComposition();
   }
 }
 
-// Used when layout.frame_rate is -1. Needs to be kept in sync with
-// DEFAULT_FRAME_RATE in nsRefreshDriver.cpp.
-static const int32_t kDefaultFrameRate = 60;
-
-static int32_t
-CalculateCompositionFrameRate()
-{
-  int32_t compositionFrameRatePref = gfxPrefs::LayersCompositionFrameRate();
-  if (compositionFrameRatePref < 0) {
-    // Use the same frame rate for composition as for layout.
-    int32_t layoutFrameRatePref = gfxPrefs::LayoutFrameRate();
-    if (layoutFrameRatePref < 0) {
-      // TODO: The main thread frame scheduling code consults the actual
-      // monitor refresh rate in this case. We should do the same.
-      return kDefaultFrameRate;
-    }
-    return layoutFrameRatePref;
-  }
-  return compositionFrameRatePref;
-}
-
-void
-CompositorParent::ScheduleSoftwareTimerComposition()
-{
-  MOZ_ASSERT(!mCompositorVsyncObserver);
-
-  if (mCurrentCompositeTask) {
-    return;
-  }
-
-  bool initialComposition = mLastCompose.IsNull();
-  TimeDuration delta;
-  if (!initialComposition)
-    delta = TimeStamp::Now() - mLastCompose;
-
-  int32_t rate = CalculateCompositionFrameRate();
-
-  // If rate == 0 (ASAP mode), minFrameDelta must be 0 so there's no delay.
-  TimeDuration minFrameDelta = TimeDuration::FromMilliseconds(
-    rate == 0 ? 0.0 : std::max(0.0, 1000.0 / rate));
-
-  mCurrentCompositeTask = NewRunnableMethod(this,
-                                            &CompositorParent::CompositeCallback,
-                                            TimeStamp::Now());
-
-  if (!initialComposition && delta < minFrameDelta) {
-    TimeDuration delay = minFrameDelta - delta;
-#ifdef COMPOSITOR_PERFORMANCE_WARNING
-    mExpectedComposeStartTime = TimeStamp::Now() + delay;
-#endif
-    ScheduleTask(mCurrentCompositeTask, delay.ToMilliseconds());
-  } else {
-#ifdef COMPOSITOR_PERFORMANCE_WARNING
-    mExpectedComposeStartTime = TimeStamp::Now();
-#endif
-    ScheduleTask(mCurrentCompositeTask, 0);
-  }
-}
-
 void
 CompositorParent::ScheduleComposition()
 {
   MOZ_ASSERT(IsInCompositorThread());
   if (mPaused) {
     return;
   }
 
-  if (mCompositorVsyncObserver) {
-    mCompositorVsyncObserver->SetNeedsComposite(true);
-  } else {
-    ScheduleSoftwareTimerComposition();
-  }
-}
-
-void
-CompositorParent::CompositeCallback(TimeStamp aScheduleTime)
-{
-  if (mCompositorVsyncObserver) {
-    // Align OMTA to vsync time.
-    // TODO: ensure it aligns with the refresh / start time of
-    // animations
-    mLastCompose = aScheduleTime;
-  } else {
-    mLastCompose = TimeStamp::Now();
-  }
-
-  mCurrentCompositeTask = nullptr;
-  CompositeToTarget(nullptr);
+  mCompositorScheduler->ScheduleComposition();
 }
 
 // Go down the composite layer tree, setting properties to match their
 // content-side counterparts.
 /* static */ void
 CompositorParent::SetShadowProperties(Layer* aLayer)
 {
   if (Layer* maskLayer = aLayer->GetMaskLayer()) {
@@ -983,17 +1077,17 @@ CompositorParent::CompositeToTarget(Draw
   PROFILER_LABEL("CompositorParent", "Composite",
     js::ProfileEntry::Category::GRAPHICS);
 
   MOZ_ASSERT(IsInCompositorThread(),
              "Composite can only be called on the compositor thread");
   TimeStamp start = TimeStamp::Now();
 
 #ifdef COMPOSITOR_PERFORMANCE_WARNING
-  TimeDuration scheduleDelta = TimeStamp::Now() - mExpectedComposeStartTime;
+  TimeDuration scheduleDelta = TimeStamp::Now() - mCompositorScheduler->GetExpectedComposeStartTime();
   if (scheduleDelta > TimeDuration::FromMilliseconds(2) ||
       scheduleDelta < TimeDuration::FromMilliseconds(-2)) {
     printf_stderr("Compositor: Compose starting off schedule by %4.1f ms\n",
                   scheduleDelta.ToMilliseconds());
   }
 #endif
 
   if (!CanComposite()) {
@@ -1017,17 +1111,17 @@ CompositorParent::CompositeToTarget(Draw
       mForceCompositionTask = nullptr;
     } else {
       return;
     }
   }
 
   mCompositionManager->ComputeRotation();
 
-  TimeStamp time = mIsTesting ? mTestTime : mLastCompose;
+  TimeStamp time = mIsTesting ? mTestTime : mCompositorScheduler->GetLastComposeTime();
   bool requestNextFrame = mCompositionManager->TransformShadowTree(time);
   if (requestNextFrame) {
     ScheduleComposition();
   }
 
   RenderTraceLayers(mLayerManager->GetRoot(), "0000");
 
 #ifdef MOZ_DUMP_PAINTING
@@ -1043,17 +1137,17 @@ CompositorParent::CompositeToTarget(Draw
     DidComposite();
   }
 
   if (mLayerManager->DebugOverlayWantsNextFrame()) {
     ScheduleComposition();
   }
 
 #ifdef COMPOSITOR_PERFORMANCE_WARNING
-  TimeDuration executionTime = TimeStamp::Now() - mLastCompose;
+  TimeDuration executionTime = TimeStamp::Now() - mCompositorScheduler->GetLastComposeTime();
   TimeDuration frameBudget = TimeDuration::FromMilliseconds(15);
   int32_t frameRate = CalculateCompositionFrameRate();
   if (frameRate > 0) {
     frameBudget = TimeDuration::FromSeconds(1.0 / frameRate);
   }
   if (executionTime > frameBudget) {
     printf_stderr("Compositor: Composite execution took %4.1f ms\n",
                   executionTime.ToMilliseconds());
@@ -1072,25 +1166,19 @@ CompositorParent::CompositeToTarget(Draw
 }
 
 void
 CompositorParent::ForceComposeToTarget(DrawTarget* aTarget, const gfx::IntRect* aRect)
 {
   PROFILER_LABEL("CompositorParent", "ForceComposeToTarget",
     js::ProfileEntry::Category::GRAPHICS);
 
-  if (mCompositorVsyncObserver) {
-    mCompositorVsyncObserver->OnForceComposeToTarget();
-  }
-
   AutoRestore<bool> override(mOverrideComposeReadiness);
   mOverrideComposeReadiness = true;
-
-  mLastCompose = TimeStamp::Now();
-  CompositeToTarget(aTarget, aRect);
+  mCompositorScheduler->ForceComposeToTarget(aTarget, aRect);
 }
 
 bool
 CompositorParent::CanComposite()
 {
   return mLayerManager &&
          mLayerManager->GetRoot() &&
          !mPaused;
@@ -1168,19 +1256,18 @@ CompositorParent::SetTestSampleTime(Laye
 {
   if (aTime.IsNull()) {
     return false;
   }
 
   mIsTesting = true;
   mTestTime = aTime;
 
-  bool testComposite = mCompositionManager && (mCurrentCompositeTask ||
-                                               (mCompositorVsyncObserver
-                                               && mCompositorVsyncObserver->NeedsComposite()));
+  bool testComposite = mCompositionManager &&
+                       mCompositorScheduler->NeedsComposite();
 
   // Update but only if we were already scheduled to animate
   if (testComposite) {
     AutoResolveRefLayers resolve(mCompositionManager);
     bool requestNextFrame = mCompositionManager->TransformShadowTree(aTime);
     if (!requestNextFrame) {
       CancelCurrentCompositeTask();
       // Pretend we composited in case someone is wating for this event.
@@ -1204,17 +1291,17 @@ CompositorParent::ApplyAsyncProperties(L
   // true or when called from test-only methods like
   // LayerTransactionParent::RecvGetAnimationTransform.
 
   // Synchronously update the layer tree
   if (aLayerTree->GetRoot()) {
     AutoResolveRefLayers resolve(mCompositionManager);
     SetShadowProperties(mLayerManager->GetRoot());
 
-    TimeStamp time = mIsTesting ? mTestTime : mLastCompose;
+    TimeStamp time = mIsTesting ? mTestTime : mCompositorScheduler->GetLastComposeTime();
     bool requestNextFrame =
       mCompositionManager->TransformShadowTree(time,
         AsyncCompositionManager::TransformsToSkip::APZ);
     if (!requestNextFrame) {
       CancelCurrentCompositeTask();
       // Pretend we composited in case someone is waiting for this event.
       DidComposite();
     }
--- a/gfx/layers/ipc/CompositorParent.h
+++ b/gfx/layers/ipc/CompositorParent.h
@@ -83,53 +83,124 @@ private:
   base::Thread* const mCompositorThread;
 
   static base::Thread* CreateCompositorThread();
   static void DestroyCompositorThread(base::Thread* aCompositorThread);
 
   friend class CompositorParent;
 };
 
+class CompositorScheduler
+{
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CompositorScheduler)
+  explicit CompositorScheduler(CompositorParent* aCompositorParent);
+
+  virtual void ScheduleComposition() = 0;
+  virtual void CancelCurrentCompositeTask();
+  virtual bool NeedsComposite() = 0;
+  virtual void Composite(TimeStamp aTimestamp) = 0;
+  virtual void ScheduleTask(CancelableTask*, int);
+  virtual void ResumeComposition();
+  virtual void ForceComposeToTarget(gfx::DrawTarget* aTarget, const nsIntRect* aRect);
+  virtual void ComposeToTarget(gfx::DrawTarget* aTarget, const nsIntRect* aRect = nullptr);
+  virtual void Destroy();
+
+  const TimeStamp& GetLastComposeTime()
+  {
+    return mLastCompose;
+  }
+
+#ifdef COMPOSITOR_PERFORMANCE_WARNING
+  const TimeStamp& GetExpectedComposeStartTime()
+  {
+    return mExpectedComposeStartTime;
+  }
+#endif
+
+protected:
+  virtual ~CompositorScheduler();
+
+  CompositorParent* mCompositorParent;
+  TimeStamp mLastCompose;
+  CancelableTask* mCurrentCompositeTask;
+
+#ifdef COMPOSITOR_PERFORMANCE_WARNING
+  TimeStamp mExpectedComposeStartTime;
+#endif
+};
+
+class CompositorSoftwareTimerScheduler final : public CompositorScheduler
+{
+public:
+  explicit CompositorSoftwareTimerScheduler(CompositorParent* aCompositorParent);
+
+  // from CompositorScheduler
+  virtual void ScheduleComposition() override;
+  virtual bool NeedsComposite() override;
+  virtual void Composite(TimeStamp aTimestamp) override;
+
+  void CallComposite();
+private:
+  virtual ~CompositorSoftwareTimerScheduler();
+};
+
 /**
  * Manages the vsync (de)registration and tracking on behalf of the
  * compositor when it need to paint.
  * Turns vsync notifications into scheduled composites.
  **/
 
-class CompositorVsyncObserver final : public VsyncObserver
+class CompositorVsyncScheduler final : public CompositorScheduler
 {
-  friend class CompositorParent;
-
 public:
-  explicit CompositorVsyncObserver(CompositorParent* aCompositorParent, nsIWidget* aWidget);
-  virtual bool NotifyVsync(TimeStamp aVsyncTimestamp) override;
+  explicit CompositorVsyncScheduler(CompositorParent* aCompositorParent, nsIWidget* aWidget);
+  bool NotifyVsync(TimeStamp aVsyncTimestamp);
   void SetNeedsComposite(bool aSchedule);
-  bool NeedsComposite();
-  void CancelCurrentCompositeTask();
-  void Destroy();
   void OnForceComposeToTarget();
 
+  // from CompositorScheduler
+  virtual void ScheduleComposition() override;
+  virtual void CancelCurrentCompositeTask() override;
+  virtual bool NeedsComposite() override;
+  virtual void Composite(TimeStamp aVsyncTimestamp) override;
+  virtual void ForceComposeToTarget(gfx::DrawTarget* aTarget, const nsIntRect* aRect) override;
+  virtual void Destroy() override;
+
 private:
-  virtual ~CompositorVsyncObserver();
+  virtual ~CompositorVsyncScheduler();
 
-  void Composite(TimeStamp aVsyncTimestamp);
   void NotifyCompositeTaskExecuted();
   void ObserveVsync();
   void UnobserveVsync();
   void DispatchTouchEvents(TimeStamp aVsyncTimestamp);
   void CancelCurrentSetNeedsCompositeTask();
 
+  class Observer final : public VsyncObserver
+  {
+  public:
+    explicit Observer(CompositorVsyncScheduler* aOwner);
+    virtual bool NotifyVsync(TimeStamp aVsyncTimestamp) override;
+    void Destroy();
+  private:
+    virtual ~Observer();
+
+    Mutex mMutex;
+    // Hold raw pointer to avoid mutual reference.
+    CompositorVsyncScheduler* mOwner;
+  };
+
   bool mNeedsComposite;
   bool mIsObservingVsync;
   int32_t mVsyncNotificationsSkipped;
-  nsRefPtr<CompositorParent> mCompositorParent;
+  CompositorParent* mCompositorParent;
   nsRefPtr<CompositorVsyncDispatcher> mCompositorVsyncDispatcher;
+  nsRefPtr<CompositorVsyncScheduler::Observer> mVsyncObserver;
 
   mozilla::Monitor mCurrentCompositeTaskMonitor;
-  CancelableTask* mCurrentCompositeTask;
 
   mozilla::Monitor mSetNeedsCompositeMonitor;
   CancelableTask* mSetNeedsCompositeTask;
 };
 
 class CompositorUpdateObserver
 {
 public:
@@ -140,17 +211,17 @@ public:
 protected:
   virtual ~CompositorUpdateObserver() {}
 };
 
 class CompositorParent final : public PCompositorParent,
                                public ShadowLayersManager
 {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_MAIN_THREAD_DESTRUCTION(CompositorParent)
-  friend class CompositorVsyncObserver;
+  friend class CompositorScheduler;
 
 public:
   explicit CompositorParent(nsIWidget* aWidget,
                             bool aUseExternalSurfaceSize = false,
                             int aSurfaceWidth = -1, int aSurfaceHeight = -1);
 
   // IToplevelProtocol::CloneToplevel()
   virtual IToplevelProtocol*
@@ -357,29 +428,27 @@ protected:
 
   virtual PLayerTransactionParent*
     AllocPLayerTransactionParent(const nsTArray<LayersBackend>& aBackendHints,
                                  const uint64_t& aId,
                                  TextureFactoryIdentifier* aTextureFactoryIdentifier,
                                  bool* aSuccess) override;
   virtual bool DeallocPLayerTransactionParent(PLayerTransactionParent* aLayers) override;
   virtual void ScheduleTask(CancelableTask*, int);
-  void CompositeCallback(TimeStamp aScheduleTime);
   void CompositeToTarget(gfx::DrawTarget* aTarget, const gfx::IntRect* aRect = nullptr);
   void ForceComposeToTarget(gfx::DrawTarget* aTarget, const gfx::IntRect* aRect = nullptr);
 
   void SetEGLSurfaceSize(int width, int height);
 
   void InitializeLayerManager(const nsTArray<LayersBackend>& aBackendHints);
   void PauseComposition();
   void ResumeComposition();
   void ResumeCompositionAndResize(int width, int height);
   void ForceComposition();
   void CancelCurrentCompositeTask();
-  void ScheduleSoftwareTimerComposition();
 
   /**
    * Add a compositor to the global compositor map.
    */
   static void AddCompositor(CompositorParent* compositor, uint64_t* id);
   /**
    * Remove a compositor from the global compositor map.
    */
@@ -392,23 +461,18 @@ protected:
   bool CanComposite();
 
   void DidComposite();
 
   nsRefPtr<LayerManagerComposite> mLayerManager;
   nsRefPtr<Compositor> mCompositor;
   RefPtr<AsyncCompositionManager> mCompositionManager;
   nsIWidget* mWidget;
-  CancelableTask *mCurrentCompositeTask;
-  TimeStamp mLastCompose;
   TimeStamp mTestTime;
   bool mIsTesting;
-#ifdef COMPOSITOR_PERFORMANCE_WARNING
-  TimeStamp mExpectedComposeStartTime;
-#endif
 
   uint64_t mPendingTransaction;
 
   bool mPaused;
 
   bool mUseExternalSurfaceSize;
   nsIntSize mEGLSurfaceSize;
 
@@ -419,17 +483,17 @@ protected:
   uint64_t mRootLayerTreeID;
 
   bool mOverrideComposeReadiness;
   CancelableTask* mForceCompositionTask;
 
   nsRefPtr<APZCTreeManager> mApzcTreeManager;
 
   nsRefPtr<CompositorThreadHolder> mCompositorThreadHolder;
-  nsRefPtr<CompositorVsyncObserver> mCompositorVsyncObserver;
+  nsRefPtr<CompositorScheduler> mCompositorScheduler;
 
   DISALLOW_EVIL_CONSTRUCTORS(CompositorParent);
 };
 
 } // layers
 } // mozilla
 
 #endif // mozilla_layers_CompositorParent_h
--- a/widget/gonk/GeckoTouchDispatcher.cpp
+++ b/widget/gonk/GeckoTouchDispatcher.cpp
@@ -80,40 +80,40 @@ GeckoTouchDispatcher::GeckoTouchDispatch
                        gfxPrefs::HardwareVsyncEnabled();
   mVsyncAdjust = TimeDuration::FromMilliseconds(gfxPrefs::TouchVsyncSampleAdjust());
   mMaxPredict = TimeDuration::FromMilliseconds(gfxPrefs::TouchResampleMaxPredict());
   mOldTouchThreshold = TimeDuration::FromMilliseconds(gfxPrefs::TouchResampleOldTouchThreshold());
   mDelayedVsyncThreshold = TimeDuration::FromMilliseconds(gfxPrefs::TouchResampleVsyncDelayThreshold());
 }
 
 void
-GeckoTouchDispatcher::SetCompositorVsyncObserver(mozilla::layers::CompositorVsyncObserver *aObserver)
+GeckoTouchDispatcher::SetCompositorVsyncScheduler(mozilla::layers::CompositorVsyncScheduler *aObserver)
 {
   MOZ_ASSERT(NS_IsMainThread());
   // We assume on b2g that there is only 1 CompositorParent
-  MOZ_ASSERT(mCompositorVsyncObserver == nullptr);
+  MOZ_ASSERT(mCompositorVsyncScheduler == nullptr);
   if (mResamplingEnabled) {
-    mCompositorVsyncObserver = aObserver;
+    mCompositorVsyncScheduler = aObserver;
   }
 }
 
 void
 GeckoTouchDispatcher::NotifyVsync(TimeStamp aVsyncTimestamp)
 {
   MOZ_ASSERT(mResamplingEnabled);
   layers::APZThreadUtils::AssertOnControllerThread();
   DispatchTouchMoveEvents(aVsyncTimestamp);
 }
 
 // Touch data timestamps are in milliseconds, aEventTime is in nanoseconds
 void
 GeckoTouchDispatcher::NotifyTouch(MultiTouchInput& aTouch, TimeStamp aEventTime)
 {
-  if (mCompositorVsyncObserver) {
-    mCompositorVsyncObserver->SetNeedsComposite(true);
+  if (mCompositorVsyncScheduler) {
+    mCompositorVsyncScheduler->SetNeedsComposite(true);
   }
 
   if (aTouch.mType == MultiTouchInput::MULTITOUCH_MOVE) {
     MutexAutoLock lock(mTouchQueueLock);
     if (mInflightNonMoveEvents > 0) {
       // If we have any pending non-move events, we shouldn't resample the
       // move events because we might end up dispatching events out of order.
       // Instead, fall back to a non-resampling in-order dispatch until we're
--- a/widget/gonk/GeckoTouchDispatcher.h
+++ b/widget/gonk/GeckoTouchDispatcher.h
@@ -23,17 +23,17 @@
 #include "mozilla/Mutex.h"
 #include <vector>
 #include "nsRefPtr.h"
 
 class nsIWidget;
 
 namespace mozilla {
 namespace layers {
-class CompositorVsyncObserver;
+class CompositorVsyncScheduler;
 }
 
 // Used to resample touch events whenever a vsync event occurs. It batches
 // touch moves and on every vsync, resamples the touch position to create smooth
 // scrolls. We use the Android touch resample algorithm. It uses a combination of
 // extrapolation and interpolation. The algorithm takes the vsync time and
 // subtracts mVsyncAdjust time in ms and creates a sample time. All touch events are
 // relative to this sample time. If the last touch event occurs AFTER this
@@ -47,17 +47,17 @@ class GeckoTouchDispatcher final
 
 public:
   static GeckoTouchDispatcher* GetInstance();
   void NotifyTouch(MultiTouchInput& aTouch, TimeStamp aEventTime);
   void DispatchTouchEvent(MultiTouchInput aMultiTouch);
   void DispatchTouchNonMoveEvent(MultiTouchInput aInput);
   void DispatchTouchMoveEvents(TimeStamp aVsyncTime);
   void NotifyVsync(TimeStamp aVsyncTimestamp);
-  void SetCompositorVsyncObserver(layers::CompositorVsyncObserver* aObserver);
+  void SetCompositorVsyncScheduler(layers::CompositorVsyncScheduler* aObserver);
 
 protected:
   ~GeckoTouchDispatcher() {}
 
 private:
   GeckoTouchDispatcher();
   void ResampleTouchMoves(MultiTouchInput& aOutTouch, TimeStamp vsyncTime);
   void SendTouchEvent(MultiTouchInput& aData);
@@ -85,13 +85,13 @@ private:
   TimeDuration mMinResampleTime;
 
   // Threshold if a vsync event runs too far behind touch events
   TimeDuration mDelayedVsyncThreshold;
 
   // How far ahead can vsync events get ahead of touch events.
   TimeDuration mOldTouchThreshold;
 
-  nsRefPtr<layers::CompositorVsyncObserver> mCompositorVsyncObserver;
+  nsRefPtr<layers::CompositorVsyncScheduler> mCompositorVsyncScheduler;
 };
 
 } // namespace mozilla
 #endif // GECKO_TOUCH_INPUT_DISPATCHER_h