Bug 552020. Part 1: Hook into CVDisplayLink to get vsync events on OSX. r=benwa,mstange
☠☠ backed out by 647ec1593edb ☠ ☠
authorMason Chang <mchang@mozilla.com>
Fri, 14 Nov 2014 08:31:04 -0800
changeset 215838 9fc2d99376cdb7c1873cd3f7e0d80c65590aa068
parent 215837 044d2a98a497a39f7df5b502043c4eacb440a8b1
child 215839 a7fb851f823a4452aa6311553cd5d4a84040460e
push id27827
push userryanvm@gmail.com
push dateFri, 14 Nov 2014 22:48:07 +0000
treeherdermozilla-central@acbd7b68fa8c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbenwa, mstange
bugs552020
milestone36.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 552020. Part 1: Hook into CVDisplayLink to get vsync events on OSX. r=benwa,mstange
widget/VsyncDispatcher.cpp
widget/VsyncDispatcher.h
widget/cocoa/nsAppShell.h
widget/cocoa/nsAppShell.mm
--- 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
@@ -4,16 +4,17 @@
  * 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.
  */
 
 #import <Cocoa/Cocoa.h>
+#import <CoreVideo/CoreVideo.h>
 
 #include "CustomCocoaEvents.h"
 #include "mozilla/WidgetTraceEvent.h"
 #include "nsAppShell.h"
 #include "nsCOMPtr.h"
 #include "nsIFile.h"
 #include "nsDirectoryServiceDefs.h"
 #include "nsString.h"
@@ -32,16 +33,19 @@
 #include "TextInputHandler.h"
 #include "mozilla/HangMonitor.h"
 #include "GeckoProfiler.h"
 #include "pratom.h"
 
 #include <IOKit/pwr_mgt/IOPMLib.h>
 #include "nsIDOMWakeLockListener.h"
 #include "nsIPowerManagerService.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/VsyncDispatcher.h"
+#include "gfxPrefs.h"
 
 using namespace mozilla::widget;
 
 // A wake lock listener that disables screen saver when requested by
 // Gecko. For example when we're playing video in a foreground tab we
 // don't want the screen saver to turn on.
 
 class MacWakeLockListener MOZ_FINAL : public nsIDOMMozWakeLockListener {
@@ -82,17 +86,95 @@ private:
         IOReturn result = ::IOPMAssertionRelease(mAssertionID);
         if (result != kIOReturnSuccess) {
           NS_WARNING("failed to release screensaver");
         }
       }
     }
     return NS_OK;
   }
-};
+}; // MacWakeLockListener
+
+// 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)
+{
+  VsyncSource* vsyncSource = (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 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
 
 // defined in nsCocoaWindow.mm
 extern int32_t             gXULModalLevel;
 
 static bool gAppShellMethodsSwizzled = false;
 
 @implementation GeckoNSApplication
 
@@ -282,24 +364,33 @@ nsAppShell::Init()
   NS_ENSURE_STATE(mCFRunLoop);
   ::CFRetain(mCFRunLoop);
 
   CFRunLoopSourceContext context;
   bzero(&context, sizeof(context));
   // context.version = 0;
   context.info = this;
   context.perform = ProcessGeckoEvents;
-  
+
   mCFRunLoopSource = ::CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
   NS_ENSURE_STATE(mCFRunLoopSource);
 
   ::CFRunLoopAddSource(mCFRunLoop, mCFRunLoopSource, kCFRunLoopCommonModes);
 
   rv = nsBaseAppShell::Init();
 
+  // gfxPrefs are init on the main thread and we need it super early
+  // to see if we should enable vsync aligned compositor
+  gfxPrefs::GetSingleton();
+  if (gfxPrefs::HardwareVsyncEnabled() && gfxPrefs::VsyncAlignedCompositor())
+  {
+    nsRefPtr<VsyncSource> osxVsyncSource = new OSXVsyncSource();
+    mozilla::VsyncDispatcher::GetInstance()->SetVsyncSource(osxVsyncSource);
+  }
+
 #ifndef __LP64__
   TextInputHandler::InstallPluginKeyEventsHandler();
 #endif
 
   if (!gAppShellMethodsSwizzled) {
     // We should only replace the original terminate: method if we're not
     // running in a Cocoa embedder. See bug 604901.
     if (!mRunningCocoaEmbedded) {