Bug 1278408. Report a constant vsync interval diff on windows. r=jrmuizel
authorMason Chang <mchang@mozilla.com>
Tue, 26 Jul 2016 11:12:24 -0700
changeset 306858 445065352cf0
parent 306857 7d4e238cd15e
child 306859 54e8922be9ef
push id79947
push usermchang@mozilla.com
push dateWed, 27 Jul 2016 15:36:37 +0000
treeherdermozilla-inbound@445065352cf0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjrmuizel
bugs1278408
milestone50.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 1278408. Report a constant vsync interval diff on windows. r=jrmuizel
gfx/thebes/gfxWindowsPlatform.cpp
--- a/gfx/thebes/gfxWindowsPlatform.cpp
+++ b/gfx/thebes/gfxWindowsPlatform.cpp
@@ -1743,17 +1743,16 @@ public:
     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(), "GFX: Could not start Windows vsync thread");
         SetVsyncRate();
       }
 
       void SetVsyncRate()
       {
         if (!gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled()) {
           mVsyncRate = TimeDuration::FromMilliseconds(1000.0 / 60.0);
@@ -1822,91 +1821,90 @@ public:
         return mVsyncRate;
       }
 
       void ScheduleSoftwareVsync(TimeStamp aVsyncTimestamp)
       {
         MOZ_ASSERT(IsInVsyncThread());
         NS_WARNING("DwmComposition dynamically disabled, falling back to software timers");
 
-        TimeStamp nextVsync = aVsyncTimestamp + mSoftwareVsyncRate;
+        TimeStamp nextVsync = aVsyncTimestamp + mVsyncRate;
         TimeDuration delay = nextVsync - TimeStamp::Now();
         if (delay.ToMilliseconds() < 0) {
           delay = mozilla::TimeDuration::FromMilliseconds(0);
         }
 
         mVsyncThread->message_loop()->PostDelayedTask(
             NewRunnableMethod(this, &D3DVsyncDisplay::VBlankLoop),
             delay.ToMilliseconds());
       }
 
-      TimeStamp GetAdjustedVsyncTimeStamp(LARGE_INTEGER& aFrequency,
-                                          QPC_TIME& aQpcVblankTime)
+      // Returns the timestamp for the just happened vsync
+      TimeStamp GetVBlankTime()
       {
         TimeStamp vsync = TimeStamp::Now();
+        TimeStamp now = vsync;
+
+        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)) {
+            return vsync;
+        }
+
+        LARGE_INTEGER frequency;
+        QueryPerformanceFrequency(&frequency);
+
         LARGE_INTEGER qpcNow;
         QueryPerformanceCounter(&qpcNow);
 
         const int microseconds = 1000000;
-        int64_t adjust = qpcNow.QuadPart - aQpcVblankTime;
-        int64_t usAdjust = (adjust * microseconds) / aFrequency.QuadPart;
+        int64_t adjust = qpcNow.QuadPart - vblankTime.qpcVBlank;
+        int64_t usAdjust = (adjust * microseconds) / frequency.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);
+          // the previously seen vsync.
+          // In these error cases, normalize to Now();
+          if (vsync >= now) {
+            vsync = vsync - mVsyncRate;
+            return vsync <= now ? vsync : now;
           }
+        }
 
-          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();
+        // If we got here on Windows 10, it means we got a weird timestamp.
+        if (vsync >= now) {
+            vsync = now;
         }
         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 + mSoftwareVsyncRate;
+        mPrevVsync = TimeStamp();
+        TimeStamp flushTime = TimeStamp::Now();
+        TimeDuration longVBlank = mVsyncRate * 2;
 
         for (;;) {
           { // scope lock
             MonitorAutoLock lock(mVsyncEnabledLock);
             if (!mVsyncEnabled) return;
           }
 
           // Large parts of gecko assume that the refresh driver timestamp
@@ -1918,47 +1916,65 @@ public:
           // 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 :/
+          // Using WaitForVBlank, the whole system dies because WaitForVBlank
+          // only works if it's run on the same thread as the Present();
           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.
+            // DWMFlush isn't working, fallback to software vsync.
             ScheduleSoftwareVsync(TimeStamp::Now());
             return;
           }
 
-          hr = WinUtils::dwmGetCompositionTimingInfoPtr(0, &vblankTime);
-          vsync = SUCCEEDED(hr) ?
-                    GetAdjustedVsyncTimeStamp(frequency, vblankTime.qpcVBlank) :
-                    TimeStamp::Now();
+          TimeStamp now = TimeStamp::Now();
+          TimeDuration flushDiff = now - flushTime;
+          flushTime = now;
+          if ((flushDiff > longVBlank) || mPrevVsync.IsNull()) {
+            // Our vblank took longer than 2 intervals, readjust our timestamps
+            vsync = GetVBlankTime();
+            mPrevVsync = vsync;
+          } else {
+            // Instead of giving the actual vsync time, a constant interval
+            // between vblanks instead of the noise generated via hardware
+            // is actually what we want. Most apps just care about the diff
+            // between vblanks to animate, so a clean constant interval is
+            // smoother.
+            vsync = mPrevVsync + mVsyncRate;
+            if (vsync > now) {
+              // DWMFlush woke up very early, so readjust our times again
+              vsync = GetVBlankTime();
+            }
+
+            if (vsync <= mPrevVsync) {
+              vsync = TimeStamp::Now();
+            }
+
+            mPrevVsync = vsync;
+          }
         } // end for
       }
 
     private:
       virtual ~D3DVsyncDisplay()
       {
         MOZ_ASSERT(NS_IsMainThread());
       }
 
       bool IsInVsyncThread()
       {
         return mVsyncThread->thread_id() == PlatformThread::CurrentId();
       }
 
-      TimeDuration mSoftwareVsyncRate;
-      TimeStamp mPrevVsync; // Only used on Windows 10
+      TimeStamp mPrevVsync;
       Monitor mVsyncEnabledLock;
       base::Thread* mVsyncThread;
       TimeDuration mVsyncRate;
       bool mVsyncEnabled;
   }; // end d3dvsyncdisplay
 
   D3DVsyncSource()
   {