Bug 1190257 - Use the previous vsync timestamp on windows 10. r=jrmuizel, a=ritu
--- a/gfx/thebes/gfxWindowsPlatform.cpp
+++ b/gfx/thebes/gfxWindowsPlatform.cpp
@@ -2145,16 +2145,17 @@ class D3DVsyncSource final : public Vsyn
public:
class D3DVsyncDisplay final : public VsyncSource::Display
{
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(D3DVsyncDisplay)
public:
D3DVsyncDisplay()
: mVsyncEnabledLock("D3DVsyncEnabledLock")
+ , mPrevVsync(TimeStamp::Now())
, 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");
}
@@ -2204,44 +2205,85 @@ public:
delay = mozilla::TimeDuration::FromMilliseconds(0);
}
mVsyncThread->message_loop()->PostDelayedTask(FROM_HERE,
NewRunnableMethod(this, &D3DVsyncDisplay::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);
+ }
+
+ 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();
+ }
+ 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 qpcNow;
LARGE_INTEGER frequency;
QueryPerformanceFrequency(&frequency);
TimeStamp vsync = TimeStamp::Now();
- TimeStamp previousVsync = vsync;
- const int microseconds = 1000000;
for (;;) {
{ // scope lock
MonitorAutoLock lock(mVsyncEnabledLock);
if (!mVsyncEnabled) return;
}
- if (previousVsync > vsync) {
- vsync = TimeStamp::Now();
- NS_WARNING("Previous vsync timestamp is ahead of the calculated vsync timestamp.");
- }
-
- previousVsync = vsync;
+ // 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 (!DwmCompositionEnabled()) {
ScheduleSoftwareVsync(vsync);
@@ -2249,23 +2291,17 @@ public:
}
// Use a combination of DwmFlush + DwmGetCompositionTimingInfoPtr
// Using WaitForVBlank, the whole system dies :/
WinUtils::dwmFlushProcPtr();
HRESULT hr = WinUtils::dwmGetCompositionTimingInfoPtr(0, &vblankTime);
vsync = TimeStamp::Now();
if (SUCCEEDED(hr)) {
- QueryPerformanceCounter(&qpcNow);
- // Adjust the timestamp to be the vsync timestamp since when
- // DwmFlush wakes up and when the actual vsync occurred are not the
- // same.
- int64_t adjust = qpcNow.QuadPart - vblankTime.qpcVBlank;
- int64_t usAdjust = (adjust * microseconds) / frequency.QuadPart;
- vsync -= TimeDuration::FromMicroseconds((double) usAdjust);
+ vsync = GetAdjustedVsyncTimeStamp(frequency, vblankTime.qpcVBlank);
}
} // end for
}
private:
virtual ~D3DVsyncDisplay()
{
MOZ_ASSERT(NS_IsMainThread());
@@ -2275,16 +2311,17 @@ public:
}
bool IsInVsyncThread()
{
return mVsyncThread->thread_id() == PlatformThread::CurrentId();
}
TimeDuration mSoftwareVsyncRate;
+ TimeStamp mPrevVsync; // Only used on Windows 10
Monitor mVsyncEnabledLock;
base::Thread* mVsyncThread;
bool mVsyncEnabled;
}; // end d3dvsyncdisplay
D3DVsyncSource()
{
mPrimaryDisplay = new D3DVsyncDisplay();
--- a/layout/base/nsRefreshDriver.cpp
+++ b/layout/base/nsRefreshDriver.cpp
@@ -358,16 +358,17 @@ private:
{
MOZ_ASSERT(NS_IsMainThread());
if (XRE_IsParentProcess()) {
MonitorAutoLock lock(mRefreshTickLock);
aVsyncTimestamp = mRecentVsync;
mProcessedVsync = true;
}
+ MOZ_ASSERT(aVsyncTimestamp <= TimeStamp::Now());
// We might have a problem that we call ~VsyncRefreshDriverTimer() before
// the scheduled TickRefreshDriver() runs. Check mVsyncRefreshDriverTimer
// before use.
if (mVsyncRefreshDriverTimer) {
mVsyncRefreshDriverTimer->RunRefreshDrivers(aVsyncTimestamp);
}
}