diff --git a/widget/VsyncDispatcher.cpp b/widget/VsyncDispatcher.cpp index ecdbeb9b9ad0..84da26eec1c2 100644 --- a/widget/VsyncDispatcher.cpp +++ b/widget/VsyncDispatcher.cpp @@ -41,6 +41,12 @@ VsyncDispatcher::~VsyncDispatcher() mCompositorObservers.Clear(); } +void +VsyncDispatcher::SetVsyncSource(VsyncSource* aVsyncSource) +{ + mVsyncSource = aVsyncSource; +} + void VsyncDispatcher::DispatchTouchEvents(bool aNotifiedCompositors, TimeStamp aVsyncTime) { @@ -88,7 +94,7 @@ VsyncDispatcher::AddCompositorVsyncObserver(VsyncObserver* 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); diff --git a/widget/VsyncDispatcher.h b/widget/VsyncDispatcher.h index 941b9159b1d3..797a0b426b82 100644 --- a/widget/VsyncDispatcher.h +++ b/widget/VsyncDispatcher.h @@ -21,6 +21,19 @@ 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 @@ -34,7 +47,7 @@ public: protected: VsyncObserver() {} virtual ~VsyncObserver() {} -}; +}; // VsyncObserver // VsyncDispatcher is used to dispatch vsync events to the registered observers. class VsyncDispatcher @@ -44,7 +57,13 @@ class 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); @@ -61,7 +80,8 @@ private: // Can have multiple compositors. On desktop, this is 1 compositor per window Mutex mCompositorObserverLock; nsTArray> mCompositorObservers; -}; + nsRefPtr mVsyncSource; +}; // VsyncDispatcher } // namespace mozilla diff --git a/widget/cocoa/nsAppShell.h b/widget/cocoa/nsAppShell.h index 57b1f2f08eb0..ca0f90945f70 100644 --- a/widget/cocoa/nsAppShell.h +++ b/widget/cocoa/nsAppShell.h @@ -5,7 +5,7 @@ /* * Runs the main native Cocoa run loop, interrupting it as needed to process - * Gecko events. + * Gecko events. */ #ifndef nsAppShell_h_ @@ -30,7 +30,7 @@ class nsAppShell : public nsBaseAppShell { public: NS_IMETHOD ResumeNative(void); - + nsAppShell(); nsresult Init(); diff --git a/widget/cocoa/nsAppShell.mm b/widget/cocoa/nsAppShell.mm index 2d14edea2c7b..203840f78f55 100644 --- a/widget/cocoa/nsAppShell.mm +++ b/widget/cocoa/nsAppShell.mm @@ -9,6 +9,7 @@ */ #import +#import #include "CustomCocoaEvents.h" #include "mozilla/WidgetTraceEvent.h" @@ -37,6 +38,9 @@ #include #include "nsIDOMWakeLockListener.h" #include "nsIPowerManagerService.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/VsyncDispatcher.h" +#include "gfxPrefs.h" using namespace mozilla::widget; @@ -87,7 +91,85 @@ private: } 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; @@ -287,7 +369,7 @@ nsAppShell::Init() // context.version = 0; context.info = this; context.perform = ProcessGeckoEvents; - + mCFRunLoopSource = ::CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context); NS_ENSURE_STATE(mCFRunLoopSource); @@ -295,6 +377,15 @@ nsAppShell::Init() 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 osxVsyncSource = new OSXVsyncSource(); + mozilla::VsyncDispatcher::GetInstance()->SetVsyncSource(osxVsyncSource); + } + #ifndef __LP64__ TextInputHandler::InstallPluginKeyEventsHandler(); #endif