bug 552020 Part 1: Hook into CVDisplayLink to get vsync events on OSX. r=benwa,mstange
authorMason Chang <mchang@mozilla.com>
Tue, 18 Nov 2014 13:28:42 -0800
changeset 216348 23614ce89c50605c8c6b230772a402c94498ebf6
parent 216347 6b0a44c6fcc6ed511a8ee32effa83f4d0db35773
child 216349 8da176df25abd64bfd0224fbd200cbce5b3cfabd
push id10031
push userkwierso@gmail.com
push dateWed, 19 Nov 2014 02:39:32 +0000
treeherderfx-team@42d5fe8d7f48 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbenwa, mstange
bugs552020
milestone36.0a1
bug 552020 Part 1: Hook into CVDisplayLink to get vsync events on OSX. r=benwa,mstange
gfx/thebes/gfxPlatform.cpp
gfx/thebes/gfxPlatform.h
gfx/thebes/gfxPlatformMac.cpp
gfx/thebes/gfxPlatformMac.h
widget/VsyncDispatcher.cpp
widget/VsyncDispatcher.h
widget/cocoa/nsAppShell.h
widget/cocoa/nsAppShell.mm
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -455,16 +455,20 @@ gfxPlatform::Init()
 
     // Request the imgITools service, implicitly initializing ImageLib.
     nsCOMPtr<imgITools> imgTools = do_GetService("@mozilla.org/image/tools;1");
     if (!imgTools) {
       NS_RUNTIMEABORT("Could not initialize ImageLib");
     }
 
     RegisterStrongMemoryReporter(new GfxMemoryImageReporter());
+
+    if (gfxPrefs::HardwareVsyncEnabled() && gfxPrefs::VsyncAlignedCompositor()) {
+      gPlatform->InitHardwareVsync();
+    }
 }
 
 static bool sLayersIPCIsUp = false;
 
 void
 gfxPlatform::Shutdown()
 {
     if (!gPlatform) {
--- a/gfx/thebes/gfxPlatform.h
+++ b/gfx/thebes/gfxPlatform.h
@@ -582,20 +582,25 @@ public:
 
     static bool UsesOffMainThreadCompositing();
 
     bool HasEnoughTotalSystemMemoryForSkiaGL();
 protected:
     gfxPlatform();
     virtual ~gfxPlatform();
 
-    void AppendCJKPrefLangs(eFontPrefLang aPrefLangs[], uint32_t &aLen, 
+    void AppendCJKPrefLangs(eFontPrefLang aPrefLangs[], uint32_t &aLen,
                             eFontPrefLang aCharLang, eFontPrefLang aPageLang);
 
     /**
+     * Initialized hardware vsync based on each platform.
+     */
+    virtual void InitHardwareVsync() {}
+
+    /**
      * Helper method, creates a draw target for a specific Azure backend.
      * Used by CreateOffscreenDrawTarget.
      */
     mozilla::TemporaryRef<DrawTarget>
       CreateDrawTargetForBackend(mozilla::gfx::BackendType aBackend,
                                  const mozilla::gfx::IntSize& aSize,
                                  mozilla::gfx::SurfaceFormat aFormat);
 
--- a/gfx/thebes/gfxPlatformMac.cpp
+++ b/gfx/thebes/gfxPlatformMac.cpp
@@ -12,21 +12,23 @@
 #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 "gfxPrefs.h"
 
 #include <dlfcn.h>
+#include <CoreVideo/CoreVideo.h>
 
 #include "nsCocoaFeatures.h"
 
 using namespace mozilla;
 using namespace mozilla::gfx;
 
 // cribbed from CTFontManager.h
 enum {
@@ -368,24 +370,24 @@ gfxPlatformMac::GetCommonFallbackFonts(u
     // Arial Unicode MS has lots of glyphs for obscure, use it as a last resort
     aFontList.AppendElement(kFontArialUnicodeMS);
 }
 
 uint32_t
 gfxPlatformMac::ReadAntiAliasingThreshold()
 {
     uint32_t threshold = 0;  // default == no threshold
-    
+
     // first read prefs flag to determine whether to use the setting or not
     bool useAntiAliasingThreshold = Preferences::GetBool("gfx.use_text_smoothing_setting", false);
 
     // if the pref setting is disabled, return 0 which effectively disables this feature
     if (!useAntiAliasingThreshold)
         return threshold;
-        
+
     // value set via Appearance pref panel, "Turn off text smoothing for font sizes xxx and smaller"
     CFNumberRef prefValue = (CFNumberRef)CFPreferencesCopyAppValue(CFSTR("AppleAntiAliasingThreshold"), kCFPreferencesCurrentApplication);
 
     if (prefValue) {
         if (!CFNumberGetValue(prefValue, kCFNumberIntType, &threshold)) {
             threshold = 0;
         }
         CFRelease(prefValue);
@@ -414,16 +416,101 @@ gfxPlatformMac::UseTiling()
 bool
 gfxPlatformMac::UseProgressivePaint()
 {
   // Progressive painting requires cross-process mutexes, which don't work so
   // well on OS X 10.6 so we disable there.
   return nsCocoaFeatures::OnLionOrLater() && gfxPlatform::UseProgressivePaint();
 }
 
+// This is the renderer output callback function, called on the vsync thread
+static CVReturn VsyncCallback(CVDisplayLinkRef aDisplayLink,
+                              const CVTimeStamp* aNow,
+                              const CVTimeStamp* aOutputTime,
+                              CVOptionFlags aFlagsIn,
+                              CVOptionFlags* aFlagsOut,
+                              void* aDisplayLinkContext)
+{
+  mozilla::VsyncSource* vsyncSource = (mozilla::VsyncSource*) aDisplayLinkContext;
+  if (vsyncSource->IsVsyncEnabled()) {
+    // Now refers to "Now" as in when this callback is called or when the current frame
+    // is displayed. aOutputTime is when the next frame should be displayed.
+    // Now is VERY VERY noisy, aOutputTime is in the future though.
+    int64_t timestamp = aOutputTime->hostTime;
+    mozilla::TimeStamp vsyncTime = mozilla::TimeStamp::FromSystemTime(timestamp);
+    mozilla::VsyncDispatcher::GetInstance()->NotifyVsync(vsyncTime);
+    return kCVReturnSuccess;
+  } else {
+    return kCVReturnDisplayLinkNotRunning;
+  }
+}
+
+class OSXVsyncSource MOZ_FINAL : public mozilla::VsyncSource
+{
+public:
+  OSXVsyncSource()
+  {
+    EnableVsync();
+  }
+
+  virtual void EnableVsync() MOZ_OVERRIDE
+  {
+    // 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, returning");
+      return;
+    }
+
+    // Set the renderer output callback function
+    if (CVDisplayLinkSetOutputCallback(mDisplayLink, &VsyncCallback, this) != kCVReturnSuccess) {
+      NS_WARNING("Could not set displaylink output callback");
+      return;
+    }
+
+    // Activate the display link
+    if (CVDisplayLinkStart(mDisplayLink) != kCVReturnSuccess) {
+      NS_WARNING("Could not activate the display link");
+      mDisplayLink = nullptr;
+    }
+  }
+
+  virtual void DisableVsync() MOZ_OVERRIDE
+  {
+    // Release the display link
+    if (mDisplayLink) {
+      CVDisplayLinkRelease(mDisplayLink);
+      mDisplayLink = nullptr;
+    }
+  }
+
+  virtual bool IsVsyncEnabled() MOZ_OVERRIDE
+  {
+    return mDisplayLink != nullptr;
+  }
+
+private:
+  virtual ~OSXVsyncSource()
+  {
+    DisableVsync();
+  }
+
+  // Manages the display link render thread
+  CVDisplayLinkRef   mDisplayLink;
+}; // OSXVsyncSource
+
+void
+gfxPlatformMac::InitHardwareVsync()
+{
+  nsRefPtr<VsyncSource> osxVsyncSource = new OSXVsyncSource();
+  mozilla::VsyncDispatcher::GetInstance()->SetVsyncSource(osxVsyncSource);
+}
+
 void
 gfxPlatformMac::GetPlatformCMSOutputProfile(void* &mem, size_t &size)
 {
     mem = nullptr;
     size = 0;
 
     CGColorSpaceRef cspace = ::CGDisplayCopyColorSpace(::CGMainDisplayID());
     if (!cspace) {
--- a/gfx/thebes/gfxPlatformMac.h
+++ b/gfx/thebes/gfxPlatformMac.h
@@ -67,16 +67,17 @@ public:
     virtual bool CanRenderContentToDataSurface() const MOZ_OVERRIDE {
       return true;
     }
 
     bool UseAcceleratedCanvas();
 
     virtual bool UseTiling() MOZ_OVERRIDE;
     virtual bool UseProgressivePaint() MOZ_OVERRIDE;
+    virtual void InitHardwareVsync() MOZ_OVERRIDE;
 
     // lower threshold on font anti-aliasing
     uint32_t GetAntiAliasingThreshold() { return mFontAntiAliasingThreshold; }
 
 private:
     virtual void GetPlatformCMSOutputProfile(void* &mem, size_t &size);
 
     // read in the pref value for the lower threshold on font anti-aliasing
--- a/widget/VsyncDispatcher.cpp
+++ b/widget/VsyncDispatcher.cpp
@@ -37,16 +37,22 @@ VsyncDispatcher::VsyncDispatcher()
 
 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);
   }
@@ -83,17 +89,17 @@ VsyncDispatcher::AddCompositorVsyncObser
   if (!mCompositorObservers.Contains(aVsyncObserver)) {
     mCompositorObservers.AppendElement(aVsyncObserver);
   }
 }
 
 void
 VsyncDispatcher::RemoveCompositorVsyncObserver(VsyncObserver* aVsyncObserver)
 {
-  MOZ_ASSERT(CompositorParent::IsInCompositorThread());
+  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");
   }
 }
 
--- a/widget/VsyncDispatcher.h
+++ b/widget/VsyncDispatcher.h
@@ -16,53 +16,73 @@ 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/cocoa/nsAppShell.h
+++ b/widget/cocoa/nsAppShell.h
@@ -1,16 +1,16 @@
 /* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
 /* 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/. */
 
 /*
  * Runs the main native Cocoa run loop, interrupting it as needed to process
- * Gecko events.  
+ * Gecko events.
  */
 
 #ifndef nsAppShell_h_
 #define nsAppShell_h_
 
 class nsCocoaWindow;
 
 #include "nsBaseAppShell.h"
@@ -25,17 +25,17 @@ class nsCocoaWindow;
 @end
 
 @class AppShellDelegate;
 
 class nsAppShell : public nsBaseAppShell
 {
 public:
   NS_IMETHOD ResumeNative(void);
-	
+
   nsAppShell();
 
   nsresult Init();
 
   NS_IMETHOD Run(void);
   NS_IMETHOD Exit(void);
   NS_IMETHOD OnProcessNextEvent(nsIThreadInternal *aThread, bool aMayWait,
                                 uint32_t aRecursionDepth);
--- a/widget/cocoa/nsAppShell.mm
+++ b/widget/cocoa/nsAppShell.mm
@@ -82,17 +82,17 @@ private:
         IOReturn result = ::IOPMAssertionRelease(mAssertionID);
         if (result != kIOReturnSuccess) {
           NS_WARNING("failed to release screensaver");
         }
       }
     }
     return NS_OK;
   }
-};
+}; // MacWakeLockListener
 
 // defined in nsCocoaWindow.mm
 extern int32_t             gXULModalLevel;
 
 static bool gAppShellMethodsSwizzled = false;
 
 @implementation GeckoNSApplication