Backed out changeset 82e94c16732d (bug 1101974)
authorWes Kocher <wkocher@mozilla.com>
Fri, 21 Nov 2014 10:52:24 -0800
changeset 216947 5b2f62c1faf9146e0d2673030fc9fb2b076f917c
parent 216946 6ac6c1eabd76dc3c08a710f5ad854d9e93a5def8
child 216948 7ab92d922d193944248a608e961598e1cb6a000f
child 216953 28ed24c69091982439bd31b3c7cbbee176ad165d
push id27868
push userkwierso@gmail.com
push dateSat, 22 Nov 2014 00:36:06 +0000
treeherdermozilla-central@7ab92d922d19 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1101974
milestone36.0a1
backs out82e94c16732d3c4d8e7a0abe7444e7a36a993993
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
Backed out changeset 82e94c16732d (bug 1101974)
gfx/layers/ipc/CompositorParent.cpp
gfx/layers/ipc/CompositorParent.h
widget/VsyncDispatcher.cpp
widget/VsyncDispatcher.h
widget/gonk/HwcComposer2D.cpp
widget/moz.build
--- a/gfx/layers/ipc/CompositorParent.cpp
+++ b/gfx/layers/ipc/CompositorParent.cpp
@@ -58,16 +58,17 @@
 #include "mozilla/ipc/ProtocolTypes.h"
 #include "mozilla/unused.h"
 #include "mozilla/Hal.h"
 #include "mozilla/HalTypes.h"
 #include "mozilla/StaticPtr.h"
 #ifdef MOZ_ENABLE_PROFILER_SPS
 #include "ProfilerMarkers.h"
 #endif
+#include "mozilla/VsyncDispatcher.h"
 
 #ifdef MOZ_WIDGET_GONK
 #include "GeckoTouchDispatcher.h"
 #endif
 
 namespace mozilla {
 namespace layers {
 
@@ -189,139 +190,131 @@ static Thread* CompositorThread() {
   return sCompositorThreadHolder ? sCompositorThreadHolder->GetCompositorThread() : nullptr;
 }
 
 static void SetThreadPriority()
 {
   hal::SetCurrentThreadPriority(hal::THREAD_PRIORITY_COMPOSITOR);
 }
 
-StaticRefPtr<VsyncDispatcher> sVsyncDispatcher;
-VsyncDispatcher::VsyncDispatcher(CompositorParent* aCompositorParent, VsyncSource* aVsyncSource)
+CompositorVsyncObserver::CompositorVsyncObserver(CompositorParent* aCompositorParent)
   : mNeedsComposite(false)
   , mIsObservingVsync(false)
   , mCompositorParent(aCompositorParent)
-  , mVsyncSource(aVsyncSource)
   , mCurrentCompositeTaskMonitor("CurrentCompositeTaskMonitor")
   , mCurrentCompositeTask(nullptr)
 {
-  sVsyncDispatcher = this;
+
 }
 
-VsyncDispatcher::~VsyncDispatcher()
+CompositorVsyncObserver::~CompositorVsyncObserver()
 {
   MOZ_ASSERT(NS_IsMainThread());
-  sVsyncDispatcher = nullptr;
-  DisableVsync();
+  UnobserveVsync();
   mCompositorParent = nullptr;
   mNeedsComposite = false;
   CancelCurrentCompositeTask();
 }
 
 /**
  * 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
-VsyncDispatcher::SetNeedsComposite(bool aNeedsComposite)
+CompositorVsyncObserver::SetNeedsComposite(bool aNeedsComposite)
 {
   MOZ_ASSERT(CompositorParent::IsInCompositorThread());
   mNeedsComposite = aNeedsComposite;
 
   if (!mIsObservingVsync && mNeedsComposite) {
-    EnableVsync();
+    ObserveVsync();
   }
 }
 
-/* static*/ void
-VsyncDispatcher::NotifyVsync(TimeStamp aVsyncTimestamp)
-{
-  // Called from the vsync dispatch thread
-  MOZ_ASSERT(!CompositorParent::IsInCompositorThread());
-  MOZ_ASSERT(!NS_IsMainThread());
-
-  if (sVsyncDispatcher) {
-    sVsyncDispatcher->RunVsync(aVsyncTimestamp);
-  }
-}
-
-void
-VsyncDispatcher::RunVsync(TimeStamp aVsyncTimestamp)
+bool
+CompositorVsyncObserver::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,
-                                              &VsyncDispatcher::Composite,
+                                              &CompositorVsyncObserver::Composite,
                                               aVsyncTimestamp);
     CompositorParent::CompositorLoop()->PostTask(FROM_HERE, mCurrentCompositeTask);
   }
+  return true;
 }
 
 void
-VsyncDispatcher::CancelCurrentCompositeTask()
+CompositorVsyncObserver::CancelCurrentCompositeTask()
 {
   MOZ_ASSERT(CompositorParent::IsInCompositorThread() || NS_IsMainThread());
   MonitorAutoLock lock(mCurrentCompositeTaskMonitor);
   if (mCurrentCompositeTask) {
     mCurrentCompositeTask->Cancel();
     mCurrentCompositeTask = nullptr;
   }
 }
 
 void
-VsyncDispatcher::Composite(TimeStamp aVsyncTimestamp)
+CompositorVsyncObserver::Composite(TimeStamp aVsyncTimestamp)
 {
   MOZ_ASSERT(CompositorParent::IsInCompositorThread());
   {
     MonitorAutoLock lock(mCurrentCompositeTaskMonitor);
     mCurrentCompositeTask = nullptr;
   }
 
   if (mNeedsComposite && mCompositorParent) {
     mNeedsComposite = false;
     mCompositorParent->CompositeCallback(aVsyncTimestamp);
   } else {
-    DisableVsync();
+    // We're getting vsync notifications but we don't need to composite so
+    // unregister the vsync.
+    UnobserveVsync();
   }
 
   DispatchTouchEvents(aVsyncTimestamp);
 }
 
 bool
-VsyncDispatcher::NeedsComposite()
+CompositorVsyncObserver::NeedsComposite()
 {
   MOZ_ASSERT(CompositorParent::IsInCompositorThread());
   return mNeedsComposite;
 }
 
+/**
+ * Since the vsync thread has its own locks before notifying us of vsync
+ * we can't register/unregister from the vsync thread. Any other thread is fine
+ */
 void
-VsyncDispatcher::EnableVsync()
+CompositorVsyncObserver::ObserveVsync()
 {
   MOZ_ASSERT(CompositorParent::IsInCompositorThread());
+  VsyncDispatcher::GetInstance()->AddCompositorVsyncObserver(this);
   mIsObservingVsync = true;
-  mVsyncSource->EnableVsync();
 }
 
 void
-VsyncDispatcher::DisableVsync()
+CompositorVsyncObserver::UnobserveVsync()
 {
   MOZ_ASSERT(CompositorParent::IsInCompositorThread() || NS_IsMainThread());
+  VsyncDispatcher::GetInstance()->RemoveCompositorVsyncObserver(this);
   mIsObservingVsync = false;
-  mVsyncSource->DisableVsync();
 }
 
 void
-VsyncDispatcher::DispatchTouchEvents(TimeStamp aVsyncTimestamp)
+CompositorVsyncObserver::DispatchTouchEvents(TimeStamp aVsyncTimestamp)
 {
 #ifdef MOZ_WIDGET_GONK
   if (gfxPrefs::TouchResampling()) {
     GeckoTouchDispatcher::NotifyVsync(aVsyncTimestamp);
   }
 #endif
 }
 
@@ -385,22 +378,21 @@ CompositorParent::CompositorParent(nsIWi
 
   mRootLayerTreeID = AllocateLayerTreeId();
   sIndirectLayerTrees[mRootLayerTreeID].mParent = this;
 
   if (gfxPrefs::AsyncPanZoomEnabled()) {
     mApzcTreeManager = new APZCTreeManager();
   }
 
-  gfxPlatform::GetPlatform()->ComputeTileSize();
+  if (gfxPrefs::VsyncAlignedCompositor()) {
+    mCompositorVsyncObserver = new CompositorVsyncObserver(this);
+  }
 
-  if (gfxPrefs::VsyncAlignedCompositor()) {
-    nsRefPtr<VsyncSource> platformVsyncSource = gfxPlatform::GetPlatform()->GetVsyncSource();
-    mVsyncDispatcher = new VsyncDispatcher(this, platformVsyncSource.get());
-  }
+  gfxPlatform::GetPlatform()->ComputeTileSize();
 }
 
 bool
 CompositorParent::IsInCompositorThread()
 {
   return CompositorThread() && CompositorThread()->thread_id() == PlatformThread::CurrentId();
 }
 
@@ -430,17 +422,17 @@ CompositorParent::Destroy()
   mCompositor = nullptr;
 
   mCompositionManager = nullptr;
   if (mApzcTreeManager) {
     mApzcTreeManager->ClearTree();
     mApzcTreeManager = nullptr;
   }
   sIndirectLayerTrees.erase(mRootLayerTreeID);
-  mVsyncDispatcher = nullptr;
+  mCompositorVsyncObserver = nullptr;
 }
 
 void
 CompositorParent::ForceIsFirstPaint()
 {
   mCompositionManager->ForceIsFirstPaint();
 }
 
@@ -512,18 +504,18 @@ CompositorParent::RecvMakeSnapshot(const
   RefPtr<DrawTarget> target = GetDrawTargetForDescriptor(aInSnapshot, gfx::BackendType::CAIRO);
   ForceComposeToTarget(target, &aRect);
   return true;
 }
 
 bool
 CompositorParent::RecvFlushRendering()
 {
-  if (gfxPrefs::VsyncAlignedCompositor() && mVsyncDispatcher->NeedsComposite()) {
-    mVsyncDispatcher->SetNeedsComposite(false);
+  if (gfxPrefs::VsyncAlignedCompositor() && 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.
     CancelCurrentCompositeTask();
     ForceComposeToTarget(nullptr);
   }
@@ -650,17 +642,17 @@ CompositorParent::ForceComposition()
   mForceCompositionTask = nullptr;
   ScheduleRenderOnCompositorThread();
 }
 
 void
 CompositorParent::CancelCurrentCompositeTask()
 {
   if (gfxPrefs::VsyncAlignedCompositor()) {
-    mVsyncDispatcher->CancelCurrentCompositeTask();
+    mCompositorVsyncObserver->CancelCurrentCompositeTask();
   } else if (mCurrentCompositeTask) {
     mCurrentCompositeTask->Cancel();
     mCurrentCompositeTask = nullptr;
   }
 }
 
 void
 CompositorParent::SetEGLSurfaceSize(int width, int height)
@@ -800,17 +792,17 @@ CompositorParent::ScheduleSoftwareTimerC
   }
 }
 
 void
 CompositorParent::ScheduleComposition()
 {
   MOZ_ASSERT(IsInCompositorThread());
   if (gfxPrefs::VsyncAlignedCompositor()) {
-    mVsyncDispatcher->SetNeedsComposite(true);
+    mCompositorVsyncObserver->SetNeedsComposite(true);
   } else {
     ScheduleSoftwareTimerComposition();
   }
 }
 
 void
 CompositorParent::CompositeCallback(TimeStamp aScheduleTime)
 {
@@ -1021,17 +1013,17 @@ CompositorParent::ShadowLayersUpdated(La
     // values to avoid race conditions when calling GetAnimationTransform etc.
     // (since the above SetShadowProperties will remove animation effects).
     // However, we only do this update when a composite operation is already
     // scheduled in order to better match the behavior under regular sampling
     // conditions.
     bool needTestComposite = mIsTesting && root &&
                              (mCurrentCompositeTask ||
                              (gfxPrefs::VsyncAlignedCompositor() &&
-                              mVsyncDispatcher->NeedsComposite()));
+                              mCompositorVsyncObserver->NeedsComposite()));
     if (needTestComposite) {
       AutoResolveRefLayers resolve(mCompositionManager);
       bool requestNextFrame =
         mCompositionManager->TransformShadowTree(mTestTime);
       if (!requestNextFrame) {
         CancelCurrentCompositeTask();
         // Pretend we composited in case someone is waiting for this event.
         DidComposite();
@@ -1055,17 +1047,17 @@ CompositorParent::SetTestSampleTime(Laye
     return false;
   }
 
   mIsTesting = true;
   mTestTime = aTime;
 
   bool testComposite = mCompositionManager && (mCurrentCompositeTask ||
                                                (gfxPrefs::VsyncAlignedCompositor()
-                                               && mVsyncDispatcher->NeedsComposite()));
+                                               && mCompositorVsyncObserver->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.
--- a/gfx/layers/ipc/CompositorParent.h
+++ b/gfx/layers/ipc/CompositorParent.h
@@ -30,16 +30,17 @@
 #include "mozilla/layers/LayersMessages.h"  // for TargetConfig
 #include "mozilla/layers/PCompositorParent.h"
 #include "mozilla/layers/ShadowLayersManager.h" // for ShadowLayersManager
 #include "mozilla/layers/APZTestData.h"
 #include "nsAutoPtr.h"                  // for nsRefPtr
 #include "nsISupportsImpl.h"
 #include "nsSize.h"                     // for nsIntSize
 #include "ThreadSafeRefcountingWithMainThreadDestruction.h"
+#include "mozilla/VsyncDispatcher.h"
 
 class CancelableTask;
 class MessageLoop;
 class gfxContext;
 class nsIWidget;
 
 namespace mozilla {
 namespace gfx {
@@ -83,73 +84,55 @@ private:
   base::Thread* const mCompositorThread;
 
   static base::Thread* CreateCompositorThread();
   static void DestroyCompositorThread(base::Thread* aCompositorThread);
 
   friend class CompositorParent;
 };
 
-// Controls how and when to enable/disable vsync.
-class VsyncSource
-{
-public:
-  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VsyncSource)
-  virtual void EnableVsync() = 0;
-  virtual void DisableVsync() = 0;
-  virtual bool IsVsyncEnabled() = 0;
-
-protected:
-  virtual ~VsyncSource() {}
-}; // VsyncSource
-
 /**
  * Manages the vsync (de)registration and tracking on behalf of the
  * compositor when it need to paint.
  * Turns vsync notifications into scheduled composites.
  **/
 
-class VsyncDispatcher MOZ_FINAL
+class CompositorVsyncObserver MOZ_FINAL : public VsyncObserver
 {
-  // Cleaned up on main thread along with the compositor. This has the same lifetime
-  // as the CompositorParent
-  NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_MAIN_THREAD_DESTRUCTION(VsyncDispatcher)
   friend class CompositorParent;
 
 public:
-  VsyncDispatcher(CompositorParent* aCompositorParent, VsyncSource* aVsyncSource);
-  static void NotifyVsync(TimeStamp aVsyncTimestamp);
-  void RunVsync(TimeStamp aVsyncTimestamp);
+  CompositorVsyncObserver(CompositorParent* aCompositorParent);
+  virtual bool NotifyVsync(TimeStamp aVsyncTimestamp) MOZ_OVERRIDE;
   void SetNeedsComposite(bool aSchedule);
   bool NeedsComposite();
   void CancelCurrentCompositeTask();
-
+ 
 private:
-  virtual ~VsyncDispatcher();
+  virtual ~CompositorVsyncObserver();
 
   void Composite(TimeStamp aVsyncTimestamp);
   void NotifyCompositeTaskExecuted();
-  void EnableVsync();
-  void DisableVsync();
+  void ObserveVsync();
+  void UnobserveVsync();
   void DispatchTouchEvents(TimeStamp aVsyncTimestamp);
 
   bool mNeedsComposite;
   bool mIsObservingVsync;
   nsRefPtr<CompositorParent> mCompositorParent;
-  nsRefPtr<VsyncSource> mVsyncSource;
 
   mozilla::Monitor mCurrentCompositeTaskMonitor;
   CancelableTask* mCurrentCompositeTask;
 };
 
 class CompositorParent MOZ_FINAL : public PCompositorParent,
                                    public ShadowLayersManager
 {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_MAIN_THREAD_DESTRUCTION(CompositorParent)
-  friend class VsyncDispatcher;
+  friend class CompositorVsyncObserver;
 
 public:
   explicit CompositorParent(nsIWidget* aWidget,
                             bool aUseExternalSurfaceSize = false,
                             int aSurfaceWidth = -1, int aSurfaceHeight = -1);
 
   // IToplevelProtocol::CloneToplevel()
   virtual IToplevelProtocol*
@@ -400,17 +383,17 @@ protected:
   uint64_t mRootLayerTreeID;
 
   bool mOverrideComposeReadiness;
   CancelableTask* mForceCompositionTask;
 
   nsRefPtr<APZCTreeManager> mApzcTreeManager;
 
   nsRefPtr<CompositorThreadHolder> mCompositorThreadHolder;
-  nsRefPtr<VsyncDispatcher> mVsyncDispatcher;
+  nsRefPtr<CompositorVsyncObserver> mCompositorVsyncObserver;
 
   DISALLOW_EVIL_CONSTRUCTORS(CompositorParent);
 };
 
 } // layers
 } // mozilla
 
 #endif // mozilla_layers_CompositorParent_h
new file mode 100644
--- /dev/null
+++ b/widget/VsyncDispatcher.cpp
@@ -0,0 +1,117 @@
+/* -*- Mode: C++; tab-width: 2; 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 "VsyncDispatcher.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/layers/CompositorParent.h"
+#include "gfxPrefs.h"
+
+#ifdef MOZ_ENABLE_PROFILER_SPS
+#include "GeckoProfiler.h"
+#include "ProfilerMarkers.h"
+#endif
+
+#ifdef MOZ_WIDGET_GONK
+#include "GeckoTouchDispatcher.h"
+#endif
+
+using namespace mozilla::layers;
+
+namespace mozilla {
+
+StaticRefPtr<VsyncDispatcher> sVsyncDispatcher;
+
+/*static*/ VsyncDispatcher*
+VsyncDispatcher::GetInstance()
+{
+  if (!sVsyncDispatcher) {
+    sVsyncDispatcher = new VsyncDispatcher();
+    ClearOnShutdown(&sVsyncDispatcher);
+  }
+
+  return sVsyncDispatcher;
+}
+
+VsyncDispatcher::VsyncDispatcher()
+  : mCompositorObserverLock("CompositorObserverLock")
+{
+
+}
+
+VsyncDispatcher::~VsyncDispatcher()
+{
+  MutexAutoLock lock(mCompositorObserverLock);
+  mCompositorObservers.Clear();
+}
+
+void
+VsyncDispatcher::SetVsyncSource(VsyncSource* aVsyncSource)
+{
+  mVsyncSource = aVsyncSource;
+}
+
+void
+VsyncDispatcher::DispatchTouchEvents(bool aNotifiedCompositors, TimeStamp aVsyncTime)
+{
+  // Touch events can sometimes start a composite, so make sure we dispatch touches
+  // even if we don't composite
+#ifdef MOZ_WIDGET_GONK
+  if (!aNotifiedCompositors && gfxPrefs::TouchResampling()) {
+    GeckoTouchDispatcher::NotifyVsync(aVsyncTime);
+  }
+#endif
+}
+
+void
+VsyncDispatcher::NotifyVsync(TimeStamp aVsyncTimestamp)
+{
+  bool notifiedCompositors = false;
+#ifdef MOZ_ENABLE_PROFILER_SPS
+    if (profiler_is_active()) {
+        CompositorParent::PostInsertVsyncProfilerMarker(aVsyncTimestamp);
+    }
+#endif
+
+  if (gfxPrefs::VsyncAlignedCompositor()) {
+    MutexAutoLock lock(mCompositorObserverLock);
+    notifiedCompositors = NotifyVsyncObservers(aVsyncTimestamp, mCompositorObservers);
+  }
+
+  DispatchTouchEvents(notifiedCompositors, aVsyncTimestamp);
+}
+
+bool
+VsyncDispatcher::NotifyVsyncObservers(TimeStamp aVsyncTimestamp, nsTArray<nsRefPtr<VsyncObserver>>& aObservers)
+{
+  // Callers should lock the respective lock for the aObservers before calling this function
+  for (size_t i = 0; i < aObservers.Length(); i++) {
+    aObservers[i]->NotifyVsync(aVsyncTimestamp);
+ }
+ return !aObservers.IsEmpty();
+}
+
+void
+VsyncDispatcher::AddCompositorVsyncObserver(VsyncObserver* aVsyncObserver)
+{
+  MOZ_ASSERT(CompositorParent::IsInCompositorThread());
+  MutexAutoLock lock(mCompositorObserverLock);
+  if (!mCompositorObservers.Contains(aVsyncObserver)) {
+    mCompositorObservers.AppendElement(aVsyncObserver);
+  }
+}
+
+void
+VsyncDispatcher::RemoveCompositorVsyncObserver(VsyncObserver* aVsyncObserver)
+{
+  MOZ_ASSERT(CompositorParent::IsInCompositorThread() || NS_IsMainThread());
+  MutexAutoLock lock(mCompositorObserverLock);
+  if (mCompositorObservers.Contains(aVsyncObserver)) {
+    mCompositorObservers.RemoveElement(aVsyncObserver);
+  } else {
+    NS_WARNING("Could not delete a compositor vsync observer\n");
+  }
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/widget/VsyncDispatcher.h
@@ -0,0 +1,88 @@
+/* -*- Mode: C++; tab-width: 2; 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_widget_VsyncDispatcher_h
+#define mozilla_widget_VsyncDispatcher_h
+
+#include "base/message_loop.h"
+#include "mozilla/Mutex.h"
+#include "nsISupportsImpl.h"
+#include "nsTArray.h"
+#include "ThreadSafeRefcountingWithMainThreadDestruction.h"
+
+class MessageLoop;
+
+namespace mozilla {
+class TimeStamp;
+
+namespace layers {
+class CompositorVsyncObserver;
+}
+
+// Controls how and when to enable/disable vsync.
+class VsyncSource
+{
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VsyncSource)
+  virtual void EnableVsync() = 0;
+  virtual void DisableVsync() = 0;
+  virtual bool IsVsyncEnabled() = 0;
+
+protected:
+  virtual ~VsyncSource() {}
+}; // VsyncSource
+
+class VsyncObserver
+{
+  // Must be destroyed on main thread since the compositor is as well
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_MAIN_THREAD_DESTRUCTION(VsyncObserver)
+
+public:
+  // The method called when a vsync occurs. Return true if some work was done.
+  // Vsync notifications will occur on the hardware vsync thread
+  virtual bool NotifyVsync(TimeStamp aVsyncTimestamp) = 0;
+
+protected:
+  VsyncObserver() {}
+  virtual ~VsyncObserver() {}
+}; // VsyncObserver
+
+// VsyncDispatcher is used to dispatch vsync events to the registered observers.
+class VsyncDispatcher
+{
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VsyncDispatcher)
+
+public:
+  static VsyncDispatcher* GetInstance();
+  // Called on the vsync thread when a hardware vsync occurs
+  // The aVsyncTimestamp can mean different things depending on the platform:
+  // b2g - The vsync timestamp of the previous frame that was just displayed
+  // OSX - The vsync timestamp of the upcoming frame
+  // TODO: Windows / Linux. DOCUMENT THIS WHEN IMPLEMENTING ON THOSE PLATFORMS
+  // Android: TODO
+  void NotifyVsync(TimeStamp aVsyncTimestamp);
+  void SetVsyncSource(VsyncSource* aVsyncSource);
+
+  // Compositor vsync observers must be added/removed on the compositor thread
+  void AddCompositorVsyncObserver(VsyncObserver* aVsyncObserver);
+  void RemoveCompositorVsyncObserver(VsyncObserver* aVsyncObserver);
+
+private:
+  VsyncDispatcher();
+  virtual ~VsyncDispatcher();
+  void DispatchTouchEvents(bool aNotifiedCompositors, TimeStamp aVsyncTime);
+
+  // Called on the vsync thread. Returns true if observers were notified
+  bool NotifyVsyncObservers(TimeStamp aVsyncTimestamp, nsTArray<nsRefPtr<VsyncObserver>>& aObservers);
+
+  // Can have multiple compositors. On desktop, this is 1 compositor per window
+  Mutex mCompositorObserverLock;
+  nsTArray<nsRefPtr<VsyncObserver>> mCompositorObservers;
+  nsRefPtr<VsyncSource> mVsyncSource;
+}; // VsyncDispatcher
+
+} // namespace mozilla
+
+#endif // __mozilla_widget_VsyncDispatcher_h
--- a/widget/gonk/HwcComposer2D.cpp
+++ b/widget/gonk/HwcComposer2D.cpp
@@ -232,17 +232,17 @@ void
 HwcComposer2D::Vsync(int aDisplay, nsecs_t aVsyncTimestamp)
 {
     TimeStamp vsyncTime = mozilla::TimeStamp::FromSystemTime(aVsyncTimestamp);
     nsecs_t vsyncInterval = aVsyncTimestamp - mLastVsyncTime;
     if (vsyncInterval < 16000000 || vsyncInterval > 17000000) {
       LOGE("Non-uniform vsync interval: %lld\n", vsyncInterval);
     }
     mLastVsyncTime = aVsyncTimestamp;
-    mozilla::layers::VsyncDispatcher::NotifyVsync(vsyncTime);
+    VsyncDispatcher::GetInstance()->NotifyVsync(vsyncTime);
 }
 
 // Called on the "invalidator" thread (run from HAL).
 void
 HwcComposer2D::Invalidate()
 {
     if (!Initialized()) {
         LOGE("HwcComposer2D::Invalidate failed!");
--- a/widget/moz.build
+++ b/widget/moz.build
@@ -118,16 +118,17 @@ EXPORTS.mozilla += [
     'EventClassList.h',
     'EventForwards.h',
     'LookAndFeel.h',
     'MiscEvents.h',
     'MouseEvents.h',
     'TextEvents.h',
     'TextRange.h',
     'TouchEvents.h',
+    'VsyncDispatcher.h',
     'WidgetUtils.h',
 ]
 
 UNIFIED_SOURCES += [
     'ContentHelper.cpp',
     'GfxDriverInfo.cpp',
     'GfxInfoBase.cpp',
     'GfxInfoCollector.cpp',
@@ -151,16 +152,17 @@ UNIFIED_SOURCES += [
     'nsScreenManagerProxy.cpp',
     'nsShmImage.cpp',
     'nsTransferable.cpp',
     'nsXPLookAndFeel.cpp',
     'PluginWidgetProxy.cpp',
     'PuppetWidget.cpp',
     'ScreenProxy.cpp',
     'SharedWidgetUtils.cpp',
+    'VsyncDispatcher.cpp',
     'WidgetEventImpl.cpp',
     'WidgetUtils.cpp',
 ]
 
 # nsBaseWidget.cpp needs to be built separately because of name clashes in the OS X headers
 SOURCES += [
     'nsBaseWidget.cpp',
 ]