Bug 1184283 - convert gfxPlatform vsync impls to new VsyncSource/VsyncManager draft
authorVladimir Vukicevic <vladimir@pobox.com>
Mon, 25 Jan 2016 16:40:28 -0500
changeset 356569 f338f893a3f1c1d3cb2cb0a1ef47e3fdc7cb4f97
parent 356568 712d8711e762ab21d41c6734a0824a19dcd2980c
child 356570 29148bb40b80bc0cf3a30f6dba364c4526a8f1c4
push id16548
push userbmo:vladimir@pobox.com
push dateTue, 26 Apr 2016 17:19:15 +0000
bugs1184283
milestone49.0a1
Bug 1184283 - convert gfxPlatform vsync impls to new VsyncSource/VsyncManager
gfx/layers/composite/AsyncCompositionManager.cpp
gfx/thebes/gfxAndroidPlatform.cpp
gfx/thebes/gfxAndroidPlatform.h
gfx/thebes/gfxPlatform.cpp
gfx/thebes/gfxPlatform.h
gfx/thebes/gfxPlatformMac.cpp
gfx/thebes/gfxPlatformMac.h
gfx/thebes/gfxWindowsPlatform.cpp
gfx/thebes/gfxWindowsPlatform.h
gfx/thebes/moz.build
widget/gonk/HwcComposer2D.cpp
widget/gonk/HwcComposer2D.h
widget/gonk/nsScreenManagerGonk.cpp
--- a/gfx/layers/composite/AsyncCompositionManager.cpp
+++ b/gfx/layers/composite/AsyncCompositionManager.cpp
@@ -39,17 +39,17 @@
 #include "gfxPrefs.h"
 #if defined(MOZ_WIDGET_ANDROID)
 # include <android/log.h>
 # include "AndroidBridge.h"
 #endif
 #include "GeckoProfiler.h"
 #include "FrameUniformityData.h"
 #include "TreeTraversal.h"
-#include "VsyncSource.h"
+#include "gfxVsync.h"
 
 struct nsCSSValueSharedList;
 
 namespace mozilla {
 namespace layers {
 
 using namespace mozilla::gfx;
 
@@ -1420,17 +1420,18 @@ AsyncCompositionManager::TransformShadow
           TransformScrollableLayer(scrollableLayers[i]);
         }
       }
     }
 
     // Advance APZ animations to the next expected vsync timestamp, if we can
     // get it.
     TimeStamp nextFrame = aCurrentFrame;
-    TimeDuration vsyncrate = gfxPlatform::GetPlatform()->GetHardwareVsync()->GetGlobalDisplay().GetVsyncRate();
+    RefPtr<VsyncSource> vsyncSource = gfxPlatform::GetPlatform()->GetHardwareVsync()->GetGlobalDisplaySource();
+    TimeDuration vsyncrate = vsyncSource->GetVsyncInterval();
     if (vsyncrate != TimeDuration::Forever()) {
       nextFrame += vsyncrate;
     }
     wantNextFrame |= SampleAPZAnimations(LayerMetricsWrapper(root), nextFrame);
   }
 
   LayerComposite* rootComposite = root->AsLayerComposite();
 
--- a/gfx/thebes/gfxAndroidPlatform.cpp
+++ b/gfx/thebes/gfxAndroidPlatform.cpp
@@ -18,17 +18,17 @@
 #include "mozilla/dom/ContentChild.h"
 #include "nsXULAppAPI.h"
 #include "nsIScreen.h"
 #include "nsIScreenManager.h"
 #include "nsILocaleService.h"
 #include "nsServiceManagerUtils.h"
 #include "gfxPrefs.h"
 #include "cairo.h"
-#include "VsyncSource.h"
+#include "gfxVsync.h"
 
 #ifdef MOZ_WIDGET_ANDROID
 #include "AndroidBridge.h"
 #endif
 
 #ifdef MOZ_WIDGET_GONK
 #include <cutils/properties.h>
 #include "mozilla/layers/CompositorBridgeParent.h"
@@ -357,90 +357,76 @@ gfxAndroidPlatform::RequiresLinearZoom()
            ContentChild::GetSingleton()->IsForBrowser();
 #endif
 
     NS_NOTREACHED("oops, what platform is this?");
     return gfxPlatform::RequiresLinearZoom();
 }
 
 #ifdef MOZ_WIDGET_GONK
-class GonkVsyncSource final : public VsyncSource
+class HWCVsyncSource final : public VsyncSource
 {
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(HWCVsyncSource, override)
 public:
-  GonkVsyncSource()
+  HWCVsyncSource(const nsID& aSourceID)
+    : VsyncSource(aSourceID)
+    , mVsyncEnabled(false)
   {
   }
 
-  virtual Display& GetGlobalDisplay() override
+  virtual void EnableVsync() override
   {
-    return mGlobalDisplay;
+    MOZ_ASSERT(NS_IsMainThread());
+    if (IsVsyncEnabled()) {
+      return;
+    }
+    mVsyncEnabled = HwcComposer2D::GetInstance()->EnableVsync(true);
+  }
+
+  virtual void DisableVsync() override
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    if (!IsVsyncEnabled()) {
+      return;
+    }
+    mVsyncEnabled = HwcComposer2D::GetInstance()->EnableVsync(false);
   }
 
-  class GonkDisplay final : public VsyncSource::Display
+  virtual bool IsVsyncEnabled() override
   {
-  public:
-    GonkDisplay() : mVsyncEnabled(false)
-    {
-    }
-
-    ~GonkDisplay()
-    {
-      DisableVsync();
-    }
-
-    virtual void EnableVsync() override
-    {
-      MOZ_ASSERT(NS_IsMainThread());
-      if (IsVsyncEnabled()) {
-        return;
-      }
-      mVsyncEnabled = HwcComposer2D::GetInstance()->EnableVsync(true);
-    }
-
-    virtual void DisableVsync() override
-    {
-      MOZ_ASSERT(NS_IsMainThread());
-      if (!IsVsyncEnabled()) {
-        return;
-      }
-      mVsyncEnabled = HwcComposer2D::GetInstance()->EnableVsync(false);
-    }
-
-    virtual bool IsVsyncEnabled() override
-    {
-      MOZ_ASSERT(NS_IsMainThread());
-      return mVsyncEnabled;
-    }
-  private:
-    bool mVsyncEnabled;
-  }; // GonkDisplay
-
-private:
-  virtual ~GonkVsyncSource()
-  {
+    MOZ_ASSERT(NS_IsMainThread());
+    return mVsyncEnabled;
   }
 
-  GonkDisplay mGlobalDisplay;
-}; // GonkVsyncSource
+protected:
+  ~HWCVsyncSource()
+  {
+    DisableVsync();
+  }
+
+  bool mVsyncEnabled;
+}; // HWCVsyncSource
 #endif
 
-already_AddRefed<mozilla::gfx::VsyncSource>
-gfxAndroidPlatform::CreateHardwareVsyncSource()
+already_AddRefed<mozilla::gfx::VsyncManager>
+gfxAndroidPlatform::CreateHardwareVsyncManager()
 {
     // Only enable true hardware vsync on kit-kat and L device. Jelly Bean has
     // inaccurate hardware vsync so disable on JB. Android pre-JB doesn't have
     // hardware vsync.
     // L is android version 21, L-MR1 is 22, kit-kat is 19, 20 is kit-kat for
     // wearables.
 #if defined(MOZ_WIDGET_GONK) && (ANDROID_VERSION == 19 || ANDROID_VERSION >= 21)
-    RefPtr<GonkVsyncSource> vsyncSource = new GonkVsyncSource();
-    VsyncSource::Display& display = vsyncSource->GetGlobalDisplay();
-    display.EnableVsync();
-    if (!display.IsVsyncEnabled()) {
+    RefPtr<VsyncSource> globalDisplay = new HWCVsyncSource(VsyncManager::kGlobalDisplaySourceID);
+    globalDisplay->EnableVsync();
+    if (!globalDisplay->IsVsyncEnabled()) {
         NS_WARNING("Error enabling gonk vsync. Falling back to software vsync");
-        return gfxPlatform::CreateHardwareVsyncSource();
+        return gfxPlatform::CreateSoftwareVsyncManager();
     }
-    display.DisableVsync();
-    return vsyncSource.forget();
+    globalDisplay->DisableVsync();
+
+    RefPtr<VsyncManager> vsyncManager = new VsyncManager();
+    vsyncManager->RegisterSource(globalDisplay);
+    return vsyncManager.forget();
 #else
-    return gfxPlatform::CreateHardwareVsyncSource();
+    return gfxPlatform::CreateHardwareVsyncManager();
 #endif
 }
--- a/gfx/thebes/gfxAndroidPlatform.h
+++ b/gfx/thebes/gfxAndroidPlatform.h
@@ -61,17 +61,17 @@ public:
     virtual bool RequiresLinearZoom() override;
 
     FT_Library GetFTLibrary();
 
     virtual bool CanRenderContentToDataSurface() const override {
       return true;
     }
 
-    virtual already_AddRefed<mozilla::gfx::VsyncSource> CreateHardwareVsyncSource() override;
+    virtual already_AddRefed<mozilla::gfx::VsyncManager> CreateHardwareVsyncManager() override;
 
 #ifdef MOZ_WIDGET_GONK
     virtual bool IsInGonkEmulator() const { return mIsInGonkEmulator; }
 #endif
 
     virtual bool SupportsApzTouchInput() const override {
       return true;
     }
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -127,18 +127,17 @@ class mozilla::gl::SkiaGLGlue : public G
 #include "mozilla/Preferences.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/Mutex.h"
 
 #include "nsAlgorithm.h"
 #include "nsIGfxInfo.h"
 #include "nsIXULRuntime.h"
-#include "VsyncSource.h"
-#include "SoftwareVsyncSource.h"
+#include "gfxVsync.h"
 #include "nscore.h" // for NS_FREE_PERMANENT_DATA
 #include "mozilla/dom/ContentChild.h"
 #include "gfxVR.h"
 #include "VRManagerChild.h"
 
 namespace mozilla {
 namespace layers {
 #ifdef MOZ_WIDGET_GONK
@@ -741,19 +740,19 @@ gfxPlatform::Init()
     if (!imgTools) {
       NS_RUNTIMEABORT("Could not initialize ImageLib");
     }
 
     RegisterStrongMemoryReporter(new GfxMemoryImageReporter());
 
     if (XRE_IsParentProcess()) {
       if (gfxPlatform::ForceSoftwareVsync()) {
-        gPlatform->mVsyncSource = (gPlatform)->gfxPlatform::CreateHardwareVsyncSource();
+        gPlatform->mVsyncManager = (gPlatform)->gfxPlatform::CreateHardwareVsyncManager();
       } else {
-        gPlatform->mVsyncSource = gPlatform->CreateHardwareVsyncSource();
+        gPlatform->mVsyncManager = gPlatform->CreateHardwareVsyncManager();
       }
     }
 
 #ifdef USE_SKIA
     uint32_t skiaCacheSize = GetSkiaGlyphCacheSize();
     if (skiaCacheSize != kDefaultGlyphCacheSize) {
       SkGraphics::SetFontCacheLimit(skiaCacheSize);
     }
@@ -802,17 +801,21 @@ gfxPlatform::Shutdown()
         NS_ASSERTION(gPlatform->mMemoryPressureObserver, "mMemoryPressureObserver has already gone");
         nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
         if (obs) {
             obs->RemoveObserver(gPlatform->mMemoryPressureObserver, "memory-pressure");
         }
 
         gPlatform->mMemoryPressureObserver = nullptr;
         gPlatform->mSkiaGlue = nullptr;
-        gPlatform->mVsyncSource = nullptr;
+
+        // we should have always created one
+        MOZ_ASSERT(gPlatform->mVsyncManager);
+        gPlatform->mVsyncManager->Shutdown();
+        gPlatform->mVsyncManager = nullptr;
     }
 
 #ifdef MOZ_WIDGET_ANDROID
     // Shut down the texture pool
     TexturePoolOGL::Shutdown();
 #endif
 
     // Shut down the default GL context provider.
@@ -2237,65 +2240,84 @@ gfxPlatform::UsesOffMainThreadCompositin
 
 #endif
     firstTime = false;
   }
 
   return result;
 }
 
+already_AddRefed<VsyncManager>
+gfxPlatform::CreateHardwareVsyncManager()
+{
+  NS_WARNING("Hardware Vsync support not yet implemented. Falling back to software timers");
+  return CreateSoftwareVsyncManager();
+}
+
+already_AddRefed<VsyncManager>
+gfxPlatform::CreateSoftwareVsyncManager()
+{
+  RefPtr<SoftwareVsyncSource> display = new SoftwareVsyncSource(VsyncManager::kGlobalDisplaySourceID,
+                                                                GetSoftwareVsyncInterval().ToMilliseconds());
+  RefPtr<VsyncManager> vsyncManager = new VsyncManager();
+  vsyncManager->RegisterSource(display);
+  return vsyncManager.forget();
+}
+
 /***
  * The preference "layout.frame_rate" has 3 meanings depending on the value:
  *
  * -1 = Auto (default), use hardware vsync or software vsync @ 60 hz if hw vsync fails.
  *  0 = ASAP mode - used during talos testing.
  *  X = Software vsync at a rate of X times per second.
  */
-already_AddRefed<mozilla::gfx::VsyncSource>
-gfxPlatform::CreateHardwareVsyncSource()
-{
-  NS_WARNING("Hardware Vsync support not yet implemented. Falling back to software timers");
-  RefPtr<mozilla::gfx::VsyncSource> softwareVsync = new SoftwareVsyncSource();
-  return softwareVsync.forget();
-}
 
 /* static */ bool
 gfxPlatform::IsInLayoutAsapMode()
 {
   // There are 2 modes of ASAP mode.
   // 1 is that the refresh driver and compositor are in lock step
   // the second is that the compositor goes ASAP and the refresh driver
   // goes at whatever the configurated rate is. This only checks the version
   // talos uses, which is the refresh driver and compositor are in lockstep.
-  return Preferences::GetInt("layout.frame_rate", -1) == 0;
+  return gfxPrefs::LayoutFrameRate() == 0;
 }
 
 /* static */ bool
 gfxPlatform::ForceSoftwareVsync()
 {
-  return Preferences::GetInt("layout.frame_rate", -1) > 0;
+  return gfxPrefs::LayoutFrameRate() >= 0;
+}
+
+/* static */ int32_t
+gfxPlatform::GetVsyncRate()
+{
+  if (gPlatform && gPlatform->mVsyncManager) {
+    RefPtr<VsyncSource> screen = gPlatform->mVsyncManager->GetGlobalDisplaySource();
+    TimeDuration td = screen->GetVsyncInterval();
+    if (td != TimeDuration::Forever()) {
+      return NSToIntRound(1000.0 / td.ToMilliseconds());
+    }
+  }
+  return GetSoftwareVsyncRate();
 }
 
 /* static */ int
 gfxPlatform::GetSoftwareVsyncRate()
 {
-  int preferenceRate = Preferences::GetInt("layout.frame_rate",
-                                           gfxPlatform::GetDefaultFrameRate());
-  if (preferenceRate <= 0) {
-    return gfxPlatform::GetDefaultFrameRate();
+  int preferenceRate = gfxPrefs::LayoutFrameRate();
+  if (preferenceRate == 0) {
+    return 10000; // ASAP mode
+  }
+  if (preferenceRate < 0) {
+    return 60; // "use platform rate", but this is software
   }
   return preferenceRate;
 }
 
-/* static */ int
-gfxPlatform::GetDefaultFrameRate()
-{
-  return 60;
-}
-
 void
 gfxPlatform::GetApzSupportInfo(mozilla::widget::InfoObject& aObj)
 {
   if (!gfxPlatform::AsyncPanZoomEnabled()) {
     return;
   }
 
   if (SupportsApzWheelInput()) {
--- a/gfx/thebes/gfxPlatform.h
+++ b/gfx/thebes/gfxPlatform.h
@@ -2,16 +2,17 @@
  * 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 GFX_PLATFORM_H
 #define GFX_PLATFORM_H
 
 #include "mozilla/Logging.h"
+#include "mozilla/TimeStamp.h"
 #include "mozilla/gfx/Types.h"
 #include "nsTArray.h"
 #include "nsString.h"
 #include "nsCOMPtr.h"
 #include "nsUnicodeScriptCodes.h"
 
 #include "gfxTypes.h"
 #include "gfxFontFamilyList.h"
@@ -44,17 +45,17 @@ namespace gl {
 class SkiaGLGlue;
 } // namespace gl
 namespace gfx {
 class DrawTarget;
 class SourceSurface;
 class DataSourceSurface;
 class ScaledFont;
 class DrawEventRecorder;
-class VsyncSource;
+class VsyncManager;
 class DeviceInitData;
 
 inline uint32_t
 BackendTypeBit(BackendType b)
 {
   return 1 << uint8_t(b);
 }
 
@@ -567,45 +568,57 @@ public:
     static bool UsesOffMainThreadCompositing();
 
     bool HasEnoughTotalSystemMemoryForSkiaGL();
 
     /**
      * Get the hardware vsync source for each platform.
      * Should only exist and be valid on the parent process
      */
-    virtual mozilla::gfx::VsyncSource* GetHardwareVsync() {
-      MOZ_ASSERT(mVsyncSource != nullptr);
+    virtual mozilla::gfx::VsyncManager* GetHardwareVsync() {
+      MOZ_ASSERT(mVsyncManager != nullptr);
       MOZ_ASSERT(XRE_IsParentProcess());
-      return mVsyncSource;
+      return mVsyncManager;
+    }
+
+    /**
+     * Returns the current Vsync rate/interval in use for the default display,
+     * whether hardware or software.
+     */
+    static int32_t GetVsyncRate();
+    static mozilla::TimeDuration GetVsyncInterval() {
+        return mozilla::TimeDuration::FromMilliseconds(1000.0 / double(GetVsyncRate()));
     }
 
     /**
      * True if layout rendering should use ASAP mode, which means
      * the refresh driver and compositor should render ASAP.
      * Used for talos testing purposes
      */
     static bool IsInLayoutAsapMode();
 
     /**
-     * Returns the software vsync rate to use.
+     * Returns the software vsync rate to use.  Reads layout.frame_rate.
+     * If pref > 0, returns it directly.
+     * If pref == 0, this is "ASAP" mode; it returns 10000, in an effort to
+     * composite as fast as possible.
+     * If pref < 0, this is "use the platform default rate".  As this is
+     * software, it returns 60.
      */
     static int GetSoftwareVsyncRate();
+    static mozilla::TimeDuration GetSoftwareVsyncInterval() {
+        return mozilla::TimeDuration::FromMilliseconds(1000.0 / double(GetSoftwareVsyncRate()));
+    }
 
     /**
      * Returns whether or not a custom vsync rate is set.
      */
     static bool ForceSoftwareVsync();
 
     /**
-     * Returns the default frame rate for the refresh driver / software vsync.
-     */
-    static int GetDefaultFrameRate();
-
-    /**
      * Used to test which input types are handled via APZ.
      */
     virtual bool SupportsApzWheelInput() const {
       return false;
     }
     virtual bool SupportsApzTouchInput() const {
       return false;
     }
@@ -663,25 +676,31 @@ public:
 
 protected:
     gfxPlatform();
     virtual ~gfxPlatform();
 
     /**
      * Initialized hardware vsync based on each platform.
      */
-    virtual already_AddRefed<mozilla::gfx::VsyncSource> CreateHardwareVsyncSource();
+    virtual already_AddRefed<mozilla::gfx::VsyncManager> CreateHardwareVsyncManager();
 
     // Returns whether or not layers should be accelerated by default on this platform.
     virtual bool AccelerateLayersByDefault();
 
     // Returns a prioritized list of available compositor backends for acceleration.
     virtual void GetAcceleratedCompositorBackends(nsTArray<mozilla::layers::LayersBackend>& aBackends);
 
     /**
+     * Initialize software vsync; can be overriden by the platform, but a generic
+     * timer-based impl exists.
+     */
+    virtual already_AddRefed<mozilla::gfx::VsyncManager> CreateSoftwareVsyncManager();
+
+    /**
      * Initialise the preferred and fallback canvas backends
      * aBackendBitmask specifies the backends which are acceptable to the caller.
      * The backend used is determined by aBackendBitmask and the order specified
      * by the gfx.canvas.azure.backends pref.
      */
     void InitBackendPrefs(uint32_t aCanvasBitmask, mozilla::gfx::BackendType aCanvasDefault,
                           uint32_t aContentBitmask, mozilla::gfx::BackendType aContentDefault);
 
@@ -743,17 +762,17 @@ protected:
     int32_t mWordCacheCharLimit;
 
     // max number of entries in word cache
     int32_t mWordCacheMaxEntries;
 
     uint32_t mTotalSystemMemory;
 
     // Hardware vsync source. Only valid on parent process
-    RefPtr<mozilla::gfx::VsyncSource> mVsyncSource;
+    RefPtr<mozilla::gfx::VsyncManager> mVsyncManager;
 
     RefPtr<mozilla::gfx::DrawTarget> mScreenReferenceDrawTarget;
 
 private:
     /**
      * Start up Thebes.
      */
     static void Init();
--- a/gfx/thebes/gfxPlatformMac.cpp
+++ b/gfx/thebes/gfxPlatformMac.cpp
@@ -13,26 +13,25 @@
 #include "gfxMacPlatformFontList.h"
 #include "gfxMacFont.h"
 #include "gfxCoreTextShaper.h"
 #include "gfxTextRun.h"
 #include "gfxUserFontSet.h"
 
 #include "nsTArray.h"
 #include "mozilla/Preferences.h"
-#include "mozilla/VsyncDispatcher.h"
 #include "qcms.h"
 #include "gfx2DGlue.h"
 
 #include <dlfcn.h>
 #include <CoreVideo/CoreVideo.h>
 
 #include "nsCocoaFeatures.h"
 #include "mozilla/layers/CompositorBridgeParent.h"
-#include "VsyncSource.h"
+#include "gfxVsync.h"
 
 using namespace mozilla;
 using namespace mozilla::gfx;
 
 // cribbed from CTFontManager.h
 enum {
    kAutoActivationDisabled = 1
 };
@@ -394,206 +393,194 @@ static CVReturn VsyncCallback(CVDisplayL
                               const CVTimeStamp* aNow,
                               const CVTimeStamp* aOutputTime,
                               CVOptionFlags aFlagsIn,
                               CVOptionFlags* aFlagsOut,
                               void* aDisplayLinkContext);
 
 class OSXVsyncSource final : public VsyncSource
 {
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(OSXVsyncSource, override)
 public:
-  OSXVsyncSource()
+  explicit OSXVsyncSource(const nsID& aID)
+    : VsyncSource(aID)
+    , mDisplayLink(nullptr)
+    , mVsyncInterval(TimeDuration::Forever())
   {
+    MOZ_ASSERT(NS_IsMainThread());
+    mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
   }
 
-  virtual Display& GetGlobalDisplay() override
+  static void RetryEnableVsync(nsITimer* aTimer, void* aOsxDisplay)
   {
-    return mGlobalDisplay;
+    MOZ_ASSERT(NS_IsMainThread());
+    OSXVsyncSource* osxDisplay = static_cast<OSXVsyncSource*>(aOsxDisplay);
+    MOZ_ASSERT(osxDisplay);
+    osxDisplay->EnableVsync();
   }
 
-  class OSXDisplay final : public VsyncSource::Display
+  virtual void EnableVsync() override
   {
-  public:
-    OSXDisplay()
-      : mDisplayLink(nullptr)
-    {
-      MOZ_ASSERT(NS_IsMainThread());
-      mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+    MOZ_ASSERT(NS_IsMainThread());
+    if (IsVsyncEnabled()) {
+      return;
     }
 
-    ~OSXDisplay()
-    {
-      MOZ_ASSERT(NS_IsMainThread());
-      mTimer->Cancel();
-      mTimer = nullptr;
-      DisableVsync();
+    // Create a display link capable of being used with all active displays
+    // TODO: See if we need to create an active DisplayLink for each monitor in multi-monitor
+    // situations. According to the docs, it is compatible with all displays running on the computer
+    // But if we have different monitors at different display rates, we may hit issues.
+    if (CVDisplayLinkCreateWithActiveCGDisplays(&mDisplayLink) != kCVReturnSuccess) {
+      NS_WARNING("Could not create a display link with all active displays. Retrying");
+      CVDisplayLinkRelease(mDisplayLink);
+      mDisplayLink = nullptr;
+
+      // bug 1142708 - When coming back from sleep,
+      // or when changing displays, active displays may not be ready yet,
+      // even if listening for the kIOMessageSystemHasPoweredOn event
+      // from OS X sleep notifications.
+      // Active displays are those that are drawable.
+      // bug 1144638 - When changing display configurations and getting
+      // notifications from CGDisplayReconfigurationCallBack, the
+      // callback gets called twice for each active display
+      // so it's difficult to know when all displays are active.
+      // Instead, try again soon. The delay is arbitrary. 100ms chosen
+      // because on a late 2013 15" retina, it takes about that
+      // long to come back up from sleep.
+      uint32_t delay = 100;
+      mTimer->InitWithFuncCallback(RetryEnableVsync, this, delay, nsITimer::TYPE_ONE_SHOT);
+      return;
     }
 
-    static void RetryEnableVsync(nsITimer* aTimer, void* aOsxDisplay)
-    {
-      MOZ_ASSERT(NS_IsMainThread());
-      OSXDisplay* osxDisplay = static_cast<OSXDisplay*>(aOsxDisplay);
-      MOZ_ASSERT(osxDisplay);
-      osxDisplay->EnableVsync();
+    if (CVDisplayLinkSetOutputCallback(mDisplayLink, &VsyncCallback, this) != kCVReturnSuccess) {
+      NS_WARNING("Could not set displaylink output callback");
+      CVDisplayLinkRelease(mDisplayLink);
+      mDisplayLink = nullptr;
+      return;
     }
 
-    virtual void EnableVsync() override
-    {
-      MOZ_ASSERT(NS_IsMainThread());
-      if (IsVsyncEnabled()) {
-        return;
-      }
-
-      // Create a display link capable of being used with all active displays
-      // TODO: See if we need to create an active DisplayLink for each monitor in multi-monitor
-      // situations. According to the docs, it is compatible with all displays running on the computer
-      // But if we have different monitors at different display rates, we may hit issues.
-      if (CVDisplayLinkCreateWithActiveCGDisplays(&mDisplayLink) != kCVReturnSuccess) {
-        NS_WARNING("Could not create a display link with all active displays. Retrying");
-        CVDisplayLinkRelease(mDisplayLink);
-        mDisplayLink = nullptr;
-
-        // bug 1142708 - When coming back from sleep,
-        // or when changing displays, active displays may not be ready yet,
-        // even if listening for the kIOMessageSystemHasPoweredOn event
-        // from OS X sleep notifications.
-        // Active displays are those that are drawable.
-        // bug 1144638 - When changing display configurations and getting
-        // notifications from CGDisplayReconfigurationCallBack, the
-        // callback gets called twice for each active display
-        // so it's difficult to know when all displays are active.
-        // Instead, try again soon. The delay is arbitrary. 100ms chosen
-        // because on a late 2013 15" retina, it takes about that
-        // long to come back up from sleep.
-        uint32_t delay = 100;
-        mTimer->InitWithFuncCallback(RetryEnableVsync, this, delay, nsITimer::TYPE_ONE_SHOT);
-        return;
-      }
-
-      if (CVDisplayLinkSetOutputCallback(mDisplayLink, &VsyncCallback, this) != kCVReturnSuccess) {
-        NS_WARNING("Could not set displaylink output callback");
-        CVDisplayLinkRelease(mDisplayLink);
-        mDisplayLink = nullptr;
-        return;
-      }
-
-      mPreviousTimestamp = TimeStamp::Now();
-      if (CVDisplayLinkStart(mDisplayLink) != kCVReturnSuccess) {
-        NS_WARNING("Could not activate the display link");
-        CVDisplayLinkRelease(mDisplayLink);
-        mDisplayLink = nullptr;
-      }
-
-      CVTime vsyncRate = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(mDisplayLink);
-      if (vsyncRate.flags & kCVTimeIsIndefinite) {
-        NS_WARNING("Could not get vsync rate, setting to 60.");
-        mVsyncRate = TimeDuration::FromMilliseconds(1000.0 / 60.0);
-      } else {
-        int64_t timeValue = vsyncRate.timeValue;
-        int64_t timeScale = vsyncRate.timeScale;
-        const int milliseconds = 1000;
-        float rateInMs = ((double) timeValue / (double) timeScale) * milliseconds;
-        mVsyncRate = TimeDuration::FromMilliseconds(rateInMs);
-      }
+    mPreviousTimestamp = TimeStamp::Now();
+    if (CVDisplayLinkStart(mDisplayLink) != kCVReturnSuccess) {
+      NS_WARNING("Could not activate the display link");
+      CVDisplayLinkRelease(mDisplayLink);
+      mDisplayLink = nullptr;
     }
 
-    virtual void DisableVsync() override
-    {
-      MOZ_ASSERT(NS_IsMainThread());
-      if (!IsVsyncEnabled()) {
-        return;
-      }
-
-      // Release the display link
-      if (mDisplayLink) {
-        CVDisplayLinkRelease(mDisplayLink);
-        mDisplayLink = nullptr;
-      }
-    }
-
-    virtual bool IsVsyncEnabled() override
-    {
-      MOZ_ASSERT(NS_IsMainThread());
-      return mDisplayLink != nullptr;
+    CVTime vsyncInterval = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(mDisplayLink);
+    if (vsyncInterval.flags & kCVTimeIsIndefinite) {
+      NS_WARNING("Could not get vsync rate, setting to 60.");
+      mVsyncInterval = TimeDuration::FromMilliseconds(1000.0 / 60.0);
+    } else {
+      int64_t timeValue = vsyncInterval.timeValue;
+      int64_t timeScale = vsyncInterval.timeScale;
+      const int milliseconds = 1000;
+      float rateInMs = ((double) timeValue / (double) timeScale) * milliseconds;
+      mVsyncInterval = TimeDuration::FromMilliseconds(rateInMs);
     }
+  }
 
-    virtual TimeDuration GetVsyncRate() override
-    {
-      return mVsyncRate;
+  virtual void DisableVsync() override
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    // Make sure the timer is cancelled, because it might
+    // enable vsync itself after it triggers.
+    mTimer->Cancel();
+
+    // Release the display link, if one is set up
+    if (mDisplayLink) {
+      CVDisplayLinkRelease(mDisplayLink);
+      mDisplayLink = nullptr;
     }
+  }
 
-    // The vsync timestamps given by the CVDisplayLinkCallback are
-    // in the future for the NEXT frame. Large parts of Gecko, such
-    // as animations assume a timestamp at either now or in the past.
-    // Normalize the timestamps given to the VsyncDispatchers to the vsync
-    // that just occured, not the vsync that is upcoming.
-    TimeStamp mPreviousTimestamp;
+  virtual bool IsVsyncEnabled() override
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    return mDisplayLink != nullptr;
+  }
 
-  private:
-    // Manages the display link render thread
-    CVDisplayLinkRef   mDisplayLink;
-    RefPtr<nsITimer> mTimer;
-    TimeDuration mVsyncRate;
-  }; // OSXDisplay
+  virtual TimeDuration GetVsyncInterval() override
+  {
+    return mVsyncInterval;
+  }
+
+  // The vsync timestamps given by the CVDisplayLinkCallback are
+  // in the future for the NEXT frame. Large parts of Gecko, such
+  // as animations assume a timestamp at either now or in the past.
+  // Normalize the timestamps given to the VsyncDispatchers to the vsync
+  // that just occured, not the vsync that is upcoming.
+  TimeStamp mPreviousTimestamp;
 
 private:
-  virtual ~OSXVsyncSource()
+  ~OSXVsyncSource()
   {
+    MOZ_ASSERT(NS_IsMainThread());
+    // should have been shut down
+    MOZ_ASSERT(!IsVsyncEnabled());
   }
 
-  OSXDisplay mGlobalDisplay;
+  void Shutdown() override {
+    DisableVsync();
+    VsyncSource::Shutdown();
+  }
+
+  // Manages the display link render thread
+  CVDisplayLinkRef   mDisplayLink;
+  RefPtr<nsITimer> mTimer;
+  TimeDuration mVsyncInterval;
 }; // OSXVsyncSource
 
 static CVReturn VsyncCallback(CVDisplayLinkRef aDisplayLink,
                               const CVTimeStamp* aNow,
                               const CVTimeStamp* aOutputTime,
                               CVOptionFlags aFlagsIn,
                               CVOptionFlags* aFlagsOut,
                               void* aDisplayLinkContext)
 {
   // Executed on OS X hardware vsync thread
-  OSXVsyncSource::OSXDisplay* display = (OSXVsyncSource::OSXDisplay*) aDisplayLinkContext;
+  OSXVsyncSource* display = (OSXVsyncSource*) aDisplayLinkContext;
   int64_t nextVsyncTimestamp = aOutputTime->hostTime;
 
   mozilla::TimeStamp nextVsync = mozilla::TimeStamp::FromSystemTime(nextVsyncTimestamp);
   mozilla::TimeStamp previousVsync = display->mPreviousTimestamp;
   mozilla::TimeStamp now = TimeStamp::Now();
 
   // Snow leopard sometimes sends vsync timestamps very far in the past.
   // Normalize the vsync timestamps to now.
   if (nextVsync <= previousVsync) {
     nextVsync = now;
     previousVsync = now;
   } else if (now < previousVsync) {
     // Bug 1158321 - The VsyncCallback can sometimes execute before the reported
     // vsync time. In those cases, normalize the timestamp to Now() as sending
     // timestamps in the future has undefined behavior. See the comment above
-    // OSXDisplay::mPreviousTimestamp
+    // OSXVsyncSource::mPreviousTimestamp
     previousVsync = now;
   }
 
   display->mPreviousTimestamp = nextVsync;
 
-  display->NotifyVsync(previousVsync);
+  display->OnVsync(previousVsync);
   return kCVReturnSuccess;
 }
 
-already_AddRefed<mozilla::gfx::VsyncSource>
-gfxPlatformMac::CreateHardwareVsyncSource()
+already_AddRefed<mozilla::gfx::VsyncManager>
+gfxPlatformMac::CreateHardwareVsyncManager()
 {
-  RefPtr<VsyncSource> osxVsyncSource = new OSXVsyncSource();
-  VsyncSource::Display& primaryDisplay = osxVsyncSource->GetGlobalDisplay();
-  primaryDisplay.EnableVsync();
-  if (!primaryDisplay.IsVsyncEnabled()) {
+  RefPtr<OSXVsyncSource> primaryDisplay = new OSXVsyncSource(VsyncManager::kGlobalDisplaySourceID);
+  primaryDisplay->EnableVsync();
+  if (!primaryDisplay->IsVsyncEnabled()) {
     NS_WARNING("OS X Vsync source not enabled. Falling back to software vsync.");
-    return gfxPlatform::CreateHardwareVsyncSource();
+    return gfxPlatform::CreateSoftwareVsyncManager();
   }
 
-  primaryDisplay.DisableVsync();
-  return osxVsyncSource.forget();
+  RefPtr<VsyncManager> vsyncManager = new VsyncManager();
+  vsyncManager->RegisterSource(primaryDisplay);
+  return vsyncManager.forget();
 }
 
 void
 gfxPlatformMac::GetPlatformCMSOutputProfile(void* &mem, size_t &size)
 {
     mem = nullptr;
     size = 0;
 
--- a/gfx/thebes/gfxPlatformMac.h
+++ b/gfx/thebes/gfxPlatformMac.h
@@ -8,17 +8,17 @@
 
 #include "nsTArrayForwardDeclare.h"
 #include "gfxPlatform.h"
 #include "mozilla/LookAndFeel.h"
 
 namespace mozilla {
 namespace gfx {
 class DrawTarget;
-class VsyncSource;
+class VsyncManager;
 } // namespace gfx
 } // namespace mozilla
 
 class gfxPlatformMac : public gfxPlatform {
 public:
     gfxPlatformMac();
     virtual ~gfxPlatformMac();
 
@@ -72,17 +72,17 @@ public:
     bool RequiresAcceleratedGLContextForCompositorOGL() const override {
       // On OS X in a VM, unaccelerated CompositorOGL shows black flashes, so we
       // require accelerated GL for CompositorOGL but allow unaccelerated GL for
       // BasicCompositor.
       return true;
     }
 
     virtual bool UseProgressivePaint() override;
-    virtual already_AddRefed<mozilla::gfx::VsyncSource> CreateHardwareVsyncSource() override;
+    virtual already_AddRefed<mozilla::gfx::VsyncManager> CreateHardwareVsyncManager() override;
 
     // lower threshold on font anti-aliasing
     uint32_t GetAntiAliasingThreshold() { return mFontAntiAliasingThreshold; }
 
 protected:
     bool AccelerateLayersByDefault() override;
 
 private:
--- a/gfx/thebes/gfxWindowsPlatform.cpp
+++ b/gfx/thebes/gfxWindowsPlatform.cpp
@@ -64,18 +64,19 @@
 #include <d3d11.h>
 
 #include "nsIMemoryReporter.h"
 #include <winternl.h>
 #include "d3dkmtQueryStatistics.h"
 
 #include "SurfaceCache.h"
 #include "gfxPrefs.h"
-
-#include "VsyncSource.h"
+#include "gfxUtils.h"
+
+#include "gfxVsync.h"
 #include "DriverCrashGuard.h"
 #include "mozilla/dom/ContentParent.h"
 
 using namespace mozilla;
 using namespace mozilla::gfx;
 using namespace mozilla::layers;
 using namespace mozilla::widget;
 using namespace mozilla::image;
@@ -2607,274 +2608,267 @@ gfxWindowsPlatform::DwmCompositionEnable
 
   if (FAILED(WinUtils::dwmIsCompositionEnabledPtr(&dwmEnabled))) {
     return false;
   }
 
   return dwmEnabled;
 }
 
-class D3DVsyncSource final : public VsyncSource
+class DWMVsyncSource final : public VsyncSource
 {
+    NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DWMVsyncSource, override)
 public:
-
-  class D3DVsyncDisplay final : public VsyncSource::Display
-  {
-    NS_INLINE_DECL_THREADSAFE_REFCOUNTING(D3DVsyncDisplay)
-    public:
-      D3DVsyncDisplay()
-        : mPrevVsync(TimeStamp::Now())
-        , mVsyncEnabledLock("D3DVsyncEnabledLock")
-        , mVsyncEnabled(false)
-      {
-        mVsyncThread = new base::Thread("WindowsVsyncThread");
-        const double rate = 1000 / 60.0;
-        mSoftwareVsyncRate = TimeDuration::FromMilliseconds(rate);
-        MOZ_RELEASE_ASSERT(mVsyncThread->Start(), "Could not start Windows vsync thread");
-        SetVsyncRate();
+    DWMVsyncSource(const nsID& aSourceID)
+      : VsyncSource(aSourceID)
+      , mVsyncEnabledLock("D3DVsyncEnabledLock")
+      , mPrevVsync(TimeStamp::Now())
+      , mVsyncEnabled(false)
+      , mVsyncInterval(TimeDuration::Forever())
+    {
+      mVsyncThread = new base::Thread("WindowsVsyncThread");
+      SetVsyncInterval();
+      MOZ_RELEASE_ASSERT(mVsyncThread->Start(), "Could not start Windows vsync thread");
+    }
+
+    static bool DwmCompositionEnabled() {
+        return gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled();
+    }
+
+    void SetVsyncInterval()
+    {
+      if (!DwmCompositionEnabled()) {
+        mVsyncInterval = gfxPlatform::GetSoftwareVsyncInterval();
+        return;
       }
 
-      void SetVsyncRate()
-      {
-        if (!gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled()) {
-          mVsyncRate = TimeDuration::FromMilliseconds(1000.0 / 60.0);
+      DWM_TIMING_INFO vblankTime;
+      // Make sure to init the cbSize, otherwise GetCompositionTiming will fail
+      vblankTime.cbSize = sizeof(DWM_TIMING_INFO);
+      HRESULT hr = WinUtils::dwmGetCompositionTimingInfoPtr(0, &vblankTime);
+      if (SUCCEEDED(hr)) {
+        UNSIGNED_RATIO refreshRate = vblankTime.rateRefresh;
+        // We get the rate in hertz / time, but we want the rate in ms.
+        float rate = ((float) refreshRate.uiDenominator
+                      / (float) refreshRate.uiNumerator) * 1000;
+        mVsyncInterval = TimeDuration::FromMilliseconds(rate);
+      } else {
+        mVsyncInterval = gfxPlatform::GetSoftwareVsyncInterval();
+      }
+    }
+
+    virtual bool IsVsyncEnabled() override
+    {
+      MOZ_ASSERT(NS_IsMainThread());
+      MonitorAutoLock lock(mVsyncEnabledLock);
+      return mVsyncEnabled;
+    }
+
+    virtual void EnableVsync() override
+    {
+      MOZ_ASSERT(NS_IsMainThread());
+      MOZ_ASSERT(mVsyncThread->IsRunning());
+      { // scope lock
+        MonitorAutoLock lock(mVsyncEnabledLock);
+        if (mVsyncEnabled) {
           return;
         }
-
-        DWM_TIMING_INFO vblankTime;
-        // Make sure to init the cbSize, otherwise GetCompositionTiming will fail
-        vblankTime.cbSize = sizeof(DWM_TIMING_INFO);
-        HRESULT hr = WinUtils::dwmGetCompositionTimingInfoPtr(0, &vblankTime);
-        if (SUCCEEDED(hr)) {
-          UNSIGNED_RATIO refreshRate = vblankTime.rateRefresh;
-          // We get the rate in hertz / time, but we want the rate in ms.
-          float rate = ((float) refreshRate.uiDenominator
-                       / (float) refreshRate.uiNumerator) * 1000;
-          mVsyncRate = TimeDuration::FromMilliseconds(rate);
-        } else {
-          mVsyncRate = TimeDuration::FromMilliseconds(1000.0 / 60.0);
-        }
+        mVsyncEnabled = true;
       }
 
-      virtual void EnableVsync() override
+      CancelableTask* vsyncTask = NewRunnableMethod(this, &DWMVsyncSource::VBlankLoop);
+      mVsyncThread->message_loop()->PostTask(FROM_HERE, vsyncTask);
+    }
+
+    virtual void DisableVsync() override
+    {
+      MOZ_ASSERT(NS_IsMainThread());
+      MOZ_ASSERT(mVsyncThread->IsRunning());
       {
-        MOZ_ASSERT(NS_IsMainThread());
-        MOZ_ASSERT(mVsyncThread->IsRunning());
-        { // scope lock
-          MonitorAutoLock lock(mVsyncEnabledLock);
-          if (mVsyncEnabled) {
-            return;
-          }
-          mVsyncEnabled = true;
-        }
-
-        CancelableTask* vsyncStart = NewRunnableMethod(this,
-            &D3DVsyncDisplay::VBlankLoop);
-        mVsyncThread->message_loop()->PostTask(FROM_HERE, vsyncStart);
-      }
-
-      virtual void DisableVsync() override
-      {
-        MOZ_ASSERT(NS_IsMainThread());
-        MOZ_ASSERT(mVsyncThread->IsRunning());
         MonitorAutoLock lock(mVsyncEnabledLock);
         if (!mVsyncEnabled) {
           return;
         }
         mVsyncEnabled = false;
       }
-
-      virtual bool IsVsyncEnabled() override
-      {
-        MOZ_ASSERT(NS_IsMainThread());
-        MonitorAutoLock lock(mVsyncEnabledLock);
-        return mVsyncEnabled;
-      }
-
-      virtual TimeDuration GetVsyncRate() override
-      {
-        return mVsyncRate;
-      }
-
-      void ScheduleSoftwareVsync(TimeStamp aVsyncTimestamp)
-      {
-        MOZ_ASSERT(IsInVsyncThread());
-        NS_WARNING("DwmComposition dynamically disabled, falling back to software timers");
-
-        TimeStamp nextVsync = aVsyncTimestamp + mSoftwareVsyncRate;
-        TimeDuration delay = nextVsync - TimeStamp::Now();
-        if (delay.ToMilliseconds() < 0) {
-          delay = mozilla::TimeDuration::FromMilliseconds(0);
-        }
-
-        mVsyncThread->message_loop()->PostDelayedTask(FROM_HERE,
-            NewRunnableMethod(this, &D3DVsyncDisplay::VBlankLoop),
-            delay.ToMilliseconds());
+    }
+
+    virtual TimeDuration GetVsyncInterval() override
+    {
+      return mVsyncInterval;
+    }
+
+    void ScheduleSoftwareVsync(TimeStamp aVsyncTimestamp)
+    {
+      MOZ_ASSERT(IsInVsyncThread());
+      NS_WARNING("DwmComposition dynamically disabled, falling back to software timers");
+
+      TimeStamp nextVsync = aVsyncTimestamp + mVsyncInterval;
+      TimeDuration delay = nextVsync - TimeStamp::Now();
+      if (delay.ToMilliseconds() < 0) {
+        delay = mozilla::TimeDuration::FromMilliseconds(0);
       }
 
-      TimeStamp GetAdjustedVsyncTimeStamp(LARGE_INTEGER& aFrequency,
-                                          QPC_TIME& aQpcVblankTime)
-      {
-        TimeStamp vsync = TimeStamp::Now();
-        LARGE_INTEGER qpcNow;
-        QueryPerformanceCounter(&qpcNow);
-
-        const int microseconds = 1000000;
-        int64_t adjust = qpcNow.QuadPart - aQpcVblankTime;
-        int64_t usAdjust = (adjust * microseconds) / aFrequency.QuadPart;
-        vsync -= TimeDuration::FromMicroseconds((double) usAdjust);
-
-        if (IsWin10OrLater()) {
-          // On Windows 10 and on, DWMGetCompositionTimingInfo, mostly
-          // reports the upcoming vsync time, which is in the future.
-          // It can also sometimes report a vblank time in the past.
-          // Since large parts of Gecko assume TimeStamps can't be in future,
-          // use the previous vsync.
-
-          // Windows 10 and Intel HD vsync timestamps are messy and
-          // all over the place once in a while. Most of the time,
-          // it reports the upcoming vsync. Sometimes, that upcoming
-          // vsync is in the past. Sometimes that upcoming vsync is before
-          // the previously seen vsync. Sometimes, the previous vsync
-          // is still in the future. In these error cases,
-          // we try to normalize to Now().
-          TimeStamp upcomingVsync = vsync;
-          if (upcomingVsync < mPrevVsync) {
-            // Windows can report a vsync that's before
-            // the previous one. So update it to sometime in the future.
-            upcomingVsync = TimeStamp::Now() + TimeDuration::FromMilliseconds(1);
-          }
-
-          vsync = mPrevVsync;
-          mPrevVsync = upcomingVsync;
+      mVsyncThread->message_loop()->PostDelayedTask(FROM_HERE,
+          NewRunnableMethod(this, &DWMVsyncSource::VBlankLoop),
+          delay.ToMilliseconds());
+    }
+
+    TimeStamp GetAdjustedVsyncTimeStamp(LARGE_INTEGER& aFrequency,
+                                        QPC_TIME& aQpcVblankTime)
+    {
+      TimeStamp vsync = TimeStamp::Now();
+      LARGE_INTEGER qpcNow;
+      QueryPerformanceCounter(&qpcNow);
+
+      const int microseconds = 1000000;
+      int64_t adjust = qpcNow.QuadPart - aQpcVblankTime;
+      int64_t usAdjust = (adjust * microseconds) / aFrequency.QuadPart;
+      vsync -= TimeDuration::FromMicroseconds((double) usAdjust);
+
+      if (IsWin10OrLater()) {
+        // On Windows 10 and on, DWMGetCompositionTimingInfo, mostly
+        // reports the upcoming vsync time, which is in the future.
+        // It can also sometimes report a vblank time in the past.
+        // Since large parts of Gecko assume TimeStamps can't be in future,
+        // use the previous vsync.
+
+        // Windows 10 and Intel HD vsync timestamps are messy and
+        // all over the place once in a while. Most of the time,
+        // it reports the upcoming vsync. Sometimes, that upcoming
+        // vsync is in the past. Sometimes that upcoming vsync is before
+        // the previously seen vsync. Sometimes, the previous vsync
+        // is still in the future. In these error cases,
+        // we try to normalize to Now().
+        TimeStamp upcomingVsync = vsync;
+        if (upcomingVsync < mPrevVsync) {
+          // Windows can report a vsync that's before
+          // the previous one. So update it to sometime in the future.
+          upcomingVsync = TimeStamp::Now() + TimeDuration::FromMilliseconds(1);
         }
-        // On Windows 7 and 8, DwmFlush wakes up AFTER qpcVBlankTime
-        // from DWMGetCompositionTimingInfo. We can return the adjusted vsync.
-
-        // Once in a while, the reported vsync timestamp can be in the future.
-        // Normalize the reported timestamp to now.
-        if (vsync >= TimeStamp::Now()) {
-          vsync = TimeStamp::Now();
-        }
-        return vsync;
+
+        vsync = mPrevVsync;
+        mPrevVsync = upcomingVsync;
+      }
+      // On Windows 7 and 8, DwmFlush wakes up AFTER qpcVBlankTime
+      // from DWMGetCompositionTimingInfo. We can return the adjusted vsync.
+
+      // Once in a while, the reported vsync timestamp can be in the future.
+      // Normalize the reported timestamp to now.
+      if (vsync >= TimeStamp::Now()) {
+        vsync = TimeStamp::Now();
       }
-
-      void VBlankLoop()
-      {
-        MOZ_ASSERT(IsInVsyncThread());
-        MOZ_ASSERT(sizeof(int64_t) == sizeof(QPC_TIME));
-
-        DWM_TIMING_INFO vblankTime;
-        // Make sure to init the cbSize, otherwise GetCompositionTiming will fail
-        vblankTime.cbSize = sizeof(DWM_TIMING_INFO);
-
-        LARGE_INTEGER frequency;
-        QueryPerformanceFrequency(&frequency);
-        TimeStamp vsync = TimeStamp::Now();
-        // On Windows 10, DwmGetCompositionInfo returns the upcoming vsync.
-        // See GetAdjustedVsyncTimestamp.
-        // On start, set mPrevVsync to the "next" vsync
-        // So we'll use this timestamp on the 2nd loop iteration.
-        mPrevVsync = vsync + mSoftwareVsyncRate;
-
-        for (;;) {
-          { // scope lock
-            MonitorAutoLock lock(mVsyncEnabledLock);
-            if (!mVsyncEnabled) return;
-          }
-
-          // Large parts of gecko assume that the refresh driver timestamp
-          // must be <= Now() and cannot be in the future.
-          MOZ_ASSERT(vsync <= TimeStamp::Now());
-          Display::NotifyVsync(vsync);
-
-          // DwmComposition can be dynamically enabled/disabled
-          // so we have to check every time that it's available.
-          // When it is unavailable, we fallback to software but will try
-          // to get back to dwm rendering once it's re-enabled
-          if (!gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled()) {
-            ScheduleSoftwareVsync(vsync);
-            return;
-          }
-
-          // Use a combination of DwmFlush + DwmGetCompositionTimingInfoPtr
-          // Using WaitForVBlank, the whole system dies :/
-          HRESULT hr = WinUtils::dwmFlushProcPtr();
-          if (!SUCCEEDED(hr)) {
-            // We don't actually know how long we had to wait on DWMFlush
-            // Instead of trying to calculate how long DwmFlush actually took
-            // Fallback to software vsync.
-            ScheduleSoftwareVsync(TimeStamp::Now());
-            return;
-          }
-
-          hr = WinUtils::dwmGetCompositionTimingInfoPtr(0, &vblankTime);
-          vsync = SUCCEEDED(hr) ?
-                    GetAdjustedVsyncTimeStamp(frequency, vblankTime.qpcVBlank) :
-                    TimeStamp::Now();
-        } // end for
-      }
-
-    private:
-      virtual ~D3DVsyncDisplay()
-      {
-        MOZ_ASSERT(NS_IsMainThread());
-        DisableVsync();
-        mVsyncThread->Stop();
-        delete mVsyncThread;
-      }
-
-      bool IsInVsyncThread()
-      {
-        return mVsyncThread->thread_id() == PlatformThread::CurrentId();
-      }
-
-      TimeDuration mSoftwareVsyncRate;
-      TimeStamp mPrevVsync; // Only used on Windows 10
-      Monitor mVsyncEnabledLock;
-      base::Thread* mVsyncThread;
-      TimeDuration mVsyncRate;
-      bool mVsyncEnabled;
-  }; // end d3dvsyncdisplay
-
-  D3DVsyncSource()
-  {
-    mPrimaryDisplay = new D3DVsyncDisplay();
-  }
-
-  virtual Display& GetGlobalDisplay() override
-  {
-    return *mPrimaryDisplay;
-  }
-
-private:
-  virtual ~D3DVsyncSource()
-  {
-  }
-  RefPtr<D3DVsyncDisplay> mPrimaryDisplay;
-}; // end D3DVsyncSource
-
-already_AddRefed<mozilla::gfx::VsyncSource>
-gfxWindowsPlatform::CreateHardwareVsyncSource()
+      return vsync;
+    }
+
+    void VBlankLoop()
+    {
+      MOZ_ASSERT(IsInVsyncThread());
+      MOZ_ASSERT(sizeof(int64_t) == sizeof(QPC_TIME));
+
+      DWM_TIMING_INFO vblankTime;
+      // Make sure to init the cbSize, otherwise GetCompositionTiming will fail
+      vblankTime.cbSize = sizeof(DWM_TIMING_INFO);
+
+      LARGE_INTEGER frequency;
+      QueryPerformanceFrequency(&frequency);
+      TimeStamp vsync = TimeStamp::Now();
+      // On Windows 10, DwmGetCompositionInfo returns the upcoming vsync.
+      // See GetAdjustedVsyncTimestamp.
+      // On start, set mPrevVsync to the "next" vsync
+      // So we'll use this timestamp on the 2nd loop iteration.
+      mPrevVsync = vsync + mVsyncInterval;
+
+      for (;;) {
+        { // scope lock
+          MonitorAutoLock lock(mVsyncEnabledLock);
+          if (!mVsyncEnabled) return;
+        }
+
+        // Large parts of gecko assume that the refresh driver timestamp
+        // must be <= Now() and cannot be in the future.
+        MOZ_ASSERT(vsync <= TimeStamp::Now());
+        VsyncSource::OnVsync(vsync);
+
+        // DwmComposition can be dynamically enabled/disabled
+        // so we have to check every time that it's available.
+        // When it is unavailable, we fallback to software but will try
+        // to get back to dwm rendering once it's re-enabled
+        if (!DwmCompositionEnabled()) {
+          ScheduleSoftwareVsync(vsync);
+          return;
+        }
+
+        // Use a combination of DwmFlush + DwmGetCompositionTimingInfoPtr
+        // Using WaitForVBlank, the whole system dies :/
+        HRESULT hr = WinUtils::dwmFlushProcPtr();
+        if (!SUCCEEDED(hr)) {
+          // We don't actually know how long we had to wait on DWMFlush
+          // Instead of trying to calculate how long DwmFlush actually took
+          // Fallback to software vsync.
+          ScheduleSoftwareVsync(TimeStamp::Now());
+          return;
+        }
+        hr = WinUtils::dwmGetCompositionTimingInfoPtr(0, &vblankTime);
+        vsync = SUCCEEDED(hr) ?
+          GetAdjustedVsyncTimeStamp(frequency, vblankTime.qpcVBlank) :
+          TimeStamp::Now();
+      } // end for
+    }
+
+  private:
+    virtual ~DWMVsyncSource()
+    {
+      MOZ_ASSERT(NS_IsMainThread());
+      // should have been stopped in Shutdown
+      MOZ_ASSERT(!mVsyncThread->IsRunning());
+      delete mVsyncThread;
+    }
+
+    void Shutdown() override {
+      DisableVsync();
+      mVsyncThread->Stop();
+
+      VsyncSource::Shutdown();
+    }
+
+    bool IsInVsyncThread()
+    {
+      return mVsyncThread->thread_id() == PlatformThread::CurrentId();
+    }
+
+    TimeStamp mPrevVsync; // Only used on Windows 10
+    Monitor mVsyncEnabledLock;
+    base::Thread* mVsyncThread;
+    bool mVsyncEnabled;
+    TimeDuration mVsyncInterval;
+}; // DWMVsyncSource
+
+already_AddRefed<mozilla::gfx::VsyncManager>
+gfxWindowsPlatform::CreateHardwareVsyncManager()
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   if (!WinUtils::dwmIsCompositionEnabledPtr) {
     NS_WARNING("Dwm composition not available, falling back to software vsync");
-    return gfxPlatform::CreateHardwareVsyncSource();
+    return gfxPlatform::CreateSoftwareVsyncManager();
   }
 
   BOOL dwmEnabled = false;
   WinUtils::dwmIsCompositionEnabledPtr(&dwmEnabled);
   if (!dwmEnabled) {
     NS_WARNING("DWM not enabled, falling back to software vsync");
-    return gfxPlatform::CreateHardwareVsyncSource();
+    return gfxPlatform::CreateSoftwareVsyncManager();
   }
 
-  RefPtr<VsyncSource> d3dVsyncSource = new D3DVsyncSource();
-  return d3dVsyncSource.forget();
+  RefPtr<VsyncManager> vsyncManager = new VsyncManager();
+
+  RefPtr<DWMVsyncSource> globalDisplay = new DWMVsyncSource(VsyncManager::kGlobalDisplaySourceID);
+  vsyncManager->RegisterSource(globalDisplay);
+
+  return vsyncManager.forget();
 }
 
 bool
 gfxWindowsPlatform::SupportsApzTouchInput() const
 {
   int value = gfxPrefs::TouchEventsEnabled();
   return value == 1 || value == 2;
 }
--- a/gfx/thebes/gfxWindowsPlatform.h
+++ b/gfx/thebes/gfxWindowsPlatform.h
@@ -248,17 +248,17 @@ public:
     // initialization has not been attempted, this returns
     // FeatureStatus::Unused.
     mozilla::gfx::FeatureStatus GetD3D11Status() const;
     mozilla::gfx::FeatureStatus GetD2D1Status() const;
     unsigned GetD3D11Version();
 
     void TestDeviceReset(DeviceResetReason aReason);
 
-    virtual already_AddRefed<mozilla::gfx::VsyncSource> CreateHardwareVsyncSource() override;
+    virtual already_AddRefed<mozilla::gfx::VsyncManager> CreateHardwareVsyncManager() override;
     static mozilla::Atomic<size_t> sD3D11MemoryUsed;
     static mozilla::Atomic<size_t> sD3D9MemoryUsed;
     static mozilla::Atomic<size_t> sD3D9SharedTextureUsed;
 
     void GetDeviceInitData(mozilla::gfx::DeviceInitData* aOut) override;
 
     bool SupportsPluginDirectBitmapDrawing() override {
       return true;
--- a/gfx/thebes/moz.build
+++ b/gfx/thebes/moz.build
@@ -42,19 +42,18 @@ EXPORTS += [
     'gfxSharedQuartzSurface.h',
     'gfxSkipChars.h',
     'gfxSVGGlyphs.h',
     'gfxTeeSurface.h',
     'gfxTextRun.h',
     'gfxTypes.h',
     'gfxUserFontSet.h',
     'gfxUtils.h',
+    'gfxVsync.h',
     'RoundedRect.h',
-    'SoftwareVsyncSource.h',
-    'VsyncSource.h',
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
     EXPORTS += [
         'gfxAndroidPlatform.h',
         'gfxFT2FontBase.h',
         'gfxFT2Fonts.h',
         'gfxPDFSurface.h',
@@ -231,19 +230,18 @@ UNIFIED_SOURCES += [
     'gfxRect.cpp',
     'gfxScriptItemizer.cpp',
     'gfxSkipChars.cpp',
     'gfxSVGGlyphs.cpp',
     'gfxTeeSurface.cpp',
     'gfxTextRun.cpp',
     'gfxUserFontSet.cpp',
     'gfxUtils.cpp',
+    'gfxVsync.cpp',
     'nsUnicodeRange.cpp',
-    'SoftwareVsyncSource.cpp',
-    'VsyncSource.cpp',
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
     UNIFIED_SOURCES += [
         'gfxMacPlatformFontList.mm',
     ]
 
 # We prefer to use ICU for normalization functions, but currently it is only
--- a/widget/gonk/HwcComposer2D.cpp
+++ b/widget/gonk/HwcComposer2D.cpp
@@ -31,17 +31,17 @@
 #include "mozilla/layers/PLayerTransaction.h"
 #include "mozilla/layers/ShadowLayerUtilsGralloc.h"
 #include "mozilla/layers/TextureHostOGL.h"  // for TextureHostOGL
 #include "mozilla/StaticPtr.h"
 #include "nsThreadUtils.h"
 #include "cutils/properties.h"
 #include "gfx2DGlue.h"
 #include "gfxPlatform.h"
-#include "VsyncSource.h"
+#include "gfxVsync.h"
 #include "nsScreenManagerGonk.h"
 #include "nsWindow.h"
 
 #if ANDROID_VERSION >= 17
 #include "libdisplay/DisplaySurface.h"
 #endif
 
 #ifdef LOG_TAG
@@ -116,16 +116,18 @@ HwcComposer2D::HwcComposer2D()
     GonkDisplay::NativeData data = GetGonkDisplay()->GetNativeData(GonkDisplay::DISPLAY_PRIMARY);
     ANativeWindow *win = data.mNativeWindow.get();
     win->query(win, NATIVE_WINDOW_WIDTH, &screenSize.width);
     win->query(win, NATIVE_WINDOW_HEIGHT, &screenSize.height);
     mScreenRect = gfx::IntRect(gfx::IntPoint(0, 0), screenSize);
 
     mColorFill = mHal->Query(HwcHALBase::QueryType::COLOR_FILL);
     mRBSwapSupport = mHal->Query(HwcHALBase::QueryType::RB_SWAP);
+
+    mVsyncSource = gfxPlatform::GetPlatform()->GetHardwareVsync()->GetGlobalDisplaySource();
 }
 
 HwcComposer2D::~HwcComposer2D()
 {
     free(mList);
 }
 
 HwcComposer2D*
@@ -178,17 +180,17 @@ HwcComposer2D::RegisterHwcEventCallback(
 
 void
 HwcComposer2D::Vsync(int aDisplay, nsecs_t aVsyncTimestamp)
 {
     // Only support hardware vsync on kitkat, L and up due to inaccurate timings
     // with JellyBean.
 #if (ANDROID_VERSION == 19 || ANDROID_VERSION >= 21)
     TimeStamp vsyncTime = mozilla::TimeStamp::FromSystemTime(aVsyncTimestamp);
-    gfxPlatform::GetPlatform()->GetHardwareVsync()->GetGlobalDisplay().NotifyVsync(vsyncTime);
+    mVsyncSource->OnVsync(vsyncTime);
 #else
     // If this device doesn't support vsync, this function should not be used.
     MOZ_ASSERT(false);
 #endif
 }
 
 // Called on the "invalidator" thread (run from HAL).
 void
--- a/widget/gonk/HwcComposer2D.h
+++ b/widget/gonk/HwcComposer2D.h
@@ -35,16 +35,20 @@
 class nsScreenGonk;
 
 namespace mozilla {
 
 namespace gl {
     class GLContext;
 }
 
+namespace gfx {
+class VsyncSource;
+}
+
 namespace layers {
 class CompositorBridgeParent;
 class Layer;
 }
 
 /*
  * HwcComposer2D provides a way for gecko to render frames
  * using hwcomposer.h in the AOSP HAL.
@@ -111,13 +115,14 @@ private:
     layers::FenceHandle mPrevRetireFence;
     layers::FenceHandle mPrevDisplayFence;
     nsTArray<HwcLayer>      mCachedSidebandLayers;
     nsTArray<layers::LayerComposite*> mHwcLayerMap;
     bool                    mPrepared;
     bool                    mHasHWVsync;
     layers::CompositorBridgeParent* mCompositorBridgeParent;
     Mutex mLock;
+    RefPtr<gfx::VsyncSource> mVsyncSource;
 };
 
 } // namespace mozilla
 
 #endif // mozilla_HwcComposer2D
--- a/widget/gonk/nsScreenManagerGonk.cpp
+++ b/widget/gonk/nsScreenManagerGonk.cpp
@@ -20,17 +20,17 @@
 #include "mozilla/MouseEvents.h"
 #include "mozilla/TouchEvents.h"
 #include "mozilla/Hal.h"
 #include "libdisplay/BootAnimation.h"
 #include "libdisplay/GonkDisplay.h"
 #include "nsScreenManagerGonk.h"
 #include "nsThreadUtils.h"
 #include "HwcComposer2D.h"
-#include "VsyncSource.h"
+#include "gfxVsync.h"
 #include "nsWindow.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/layers/CompositorBridgeParent.h"
 #include "mozilla/Services.h"
 #include "mozilla/ProcessPriorityManager.h"
 #include "nsIdleService.h"
 #include "nsIObserverService.h"
 #include "nsAppShell.h"
@@ -905,21 +905,21 @@ nsScreenManagerGonk::VsyncControl(bool a
         NS_DispatchToMainThread(
             NS_NewRunnableMethodWithArgs<bool>(this,
                                                &nsScreenManagerGonk::VsyncControl,
                                                aEnabled));
         return;
     }
 
     MOZ_ASSERT(NS_IsMainThread());
-    VsyncSource::Display &display = gfxPlatform::GetPlatform()->GetHardwareVsync()->GetGlobalDisplay();
+    RefPtr<VsyncSource> display = gfxPlatform::GetPlatform()->GetHardwareVsync()->GetGlobalDisplaySource();
     if (aEnabled) {
-        display.EnableVsync();
+        display->EnableVsync();
     } else {
-        display.DisableVsync();
+        display->DisableVsync();
     }
 }
 
 uint32_t
 nsScreenManagerGonk::GetIdFromType(GonkDisplay::DisplayType aDisplayType)
 {
     // This is the only place where we make the assumption that
     // display type is equivalent to screen id.