New issue
Advanced search Search tips
Note: Color blocks (like or ) mean that a user may not be available. Tooltip shows the reason.

Issue 680639 link

Starred by 3 users

Issue metadata

Status: Fixed
Owner:
not on Chrome anymore
Closed: May 2017
Cc:
Components:
EstimatedDays: ----
NextAction: ----
OS: Windows
Pri: 3
Type: Bug



Sign in to add a comment

VSync interval is wrong on a multi-monitor setup with non-60 Hz secondary monitor

Project Member Reported by stanisc@chromium.org, Jan 12 2017

Issue description

OS: Win10

What steps will reproduce the problem?
(1) I used NVidia control panel to create a custom resolution with 50 Hz refresh rate and set my secondary monitor to that resolution / refresh rate.
(2) Opened vsynctester.com in on the secondary monitor 

What is the expected result?
50 fps and 20 ms inter-frame interval

What happens instead?
I get 50 fps on average but the interval keeps jumping between 16.7 ms and some larger than 20 ms value. See the attached screenshot.

When I debugged GPU process I found that VSyncProviderWin produces 16.7 ms interval regardless of which monitor the window is on. Timebase timestamps correspond to 50 Hz framerate but the interval - to 60 Hz framerate which means a wrong value is returned by DwmGetCompositionTimingInfo.

Since the interval is used on the compositor side to schedule next delay based VSync it is easy to see why this generated this pattern.

Please note that if I force this code to take the non-DWM code path and call EnumDisplaySettings then it gets back the correct 50 Hz dmDisplayFrequency.

FYI: VSync prototype that generates actual VSync signals on GPU side works correctly with this monitor setup. I am not sure we want to fix this separately or just wait for GPU VSync to land. Correct VSync interval is still important even in this case because it is used to calculate frame deadline and might be used for video playback.



 
50hz_sync.png
440 KB View Download
I think we intentionally changed it not to reduce the interval in https://codereview.chromium.org/668273003 . Maybe we should use number from EnumDisplaySettings if it's off by more than a certain amount.
I think the following should make sense.

1) For a primary monitor - use the existing logic
2) For a secondary monitor - run the non-dwm code and override the interval if it different by more than some percentage. I think 5% threshold should probably be sufficient.

The special case #1 would probably save some CPU cycles. Whether monitor is primary or not comes from GetMonitorInfo so we won't have to call EnumDisplaySettings in that case.

Cc: -stanisc@chromium.org jbau...@chromium.org
Owner: stanisc@chromium.org
I'll take this bug and try to fix it.

Comment 5 Deleted

Comment 6 by jer...@duckware.com, Mar 16 2017

See issue 422000, where it was found that EnumDisplaySettings produces the wrong Hz all the time (it rounds the Hz to an integer value).  It does not seem like much, but the result is jank (true Hz values are virtually never true integer values).

Also, please note that a *very* small change in the Hz has a dramatic impact in offset from true vsync and causes jank.

So, what is "it" in "if it is off by a certain percentage"?  Because the attached is the result of using a Hz that is 'off' by only 0.33% (so 'it' can not be Hz).

Comment 7 by jer...@duckware.com, Mar 16 2017

attached is missing attachment
small-hz-change.jpg
34.7 KB View Download

Comment 8 by jer...@duckware.com, Mar 17 2017

stanisc, will you please reconfirm your DwmGetCompositionTimingInfo() results?

The graph you provided has the classic 'signature' of ANGLE vsync push back.  I setup a Win 8.1 notebook (intel integrated graphics) with notebook display at 60Hz and external secondary HDTV at 50Hz and then ran Chrome on the secondary display and reproduced an identical graph to yours.  And turning on the 'late' line showed that DWM vblank numbers at 60Hz.  To confirm, I ran my dwm testing tool on the secondary monitor and saw 60Hz numbers for both qpcVBlank and qpcRefreshPeriod from DWM.  Then to reconfirm in Chrome, I ran Chrome with ANGLE vsync off and got a perfect 60Hz graph on the secondary 50Hz monitor (and as expected, VSYNC indicator show failures) -- so Chrome is trying to run at the DWM rate (Hz of the primary monitor).  Turned ANGLE vsync back on, Chrome still tries to run at 60Hz, but the VSYNC indicator works, because of (apparently) ANGLE vsync push back (at 50Hz).

I then replicated these results on a second Win 8.1 notebook.

[this situation is yet another reason it would be nice of have an official command line switch to turn off ANGLE vsync]


I double checked what DwmGetCompositionTimingInfo actually returns for the vblank timestamps (qpcVBlank field). 
The pattern of deltas between qpcVBlank values looks like this:
interval = 33388
interval = 16655
interval = 16690
interval = 16660
interval = 16656
interval = 33400
interval = 16657
interval = 16636
interval = 16701
interval = 16669
interval = 33369
interval = 16656
interval = 16648
interval = 16699
interval = 16670
interval = 33349

Indeed this shoes that qpcVBlank syncs to 60 Hz DWM rather than 50 Hz secondary monitor and achieves the average 50 Hz by skipping some cycles.

I am working on another quick prototype that will use IDXGIOutput::WaitForVBlank and IDXGIOutput::GetFrameStatistics methods to wait for VBlank and get v-sync time. That won't fix the original problem with VSyncProvider but at least will tell me if GetFrameStatistics is a better method than DwmGetCompositionTimingInfo to get the timing info. 

I did a few experiments and the result is the following:

1) No matter what kind of "GetFrameStatistics" method I use the values are always locked to DWM refresh rate.

2) I am inclined to not fix this for the software-based v-sync - the one that uses VSyncProviderWin / DWM to get timestamps and intervals.

3) For D3D VSync, I did a prototype that avoids leveraging existing VSyncProviderWin / DWM to get timestamps and intervals and instead just uses now() for the timestamps and averaged deltas between now() times for the intervals. To get really smooth and stable intervals I tried using a rolling overage over a history of last 100 deltas and that seems to be pretty good.

What is more important the current software-based v-sync ends up being very jerky when there is an animation (or game) on one monitor and a video played on another monitor (with these monitors running at different refresh rates). With the D3D v-sync with the prototype mentioned above it remains smooth on both monitors.

So I think I'll do the following:
1) Stop using DWM for v-sync interval. Use a rolling average over last N deltas between wake-up times where N is fairly large.
2) For v-sync timestamps use wake-up time by default. I think it might be OK to use DWM time when running on primary monitor only. The logic could potentially be more complex, like use DWM on a secondary monitor too when it has the same refresh rate, but implementing that reliably seems a bit tricky. It looks like the difference between DWM time and wake-up time is generally tiny - something like 30 us.
stanisc/10: Regarding 'intervals', what does Chrome use the interval for internally?  And does a variation of 100 us in the interval even (really) matter?  If it does matter, you should probably run the  intervals through a simple filter that eliminates spikes and dips -- so that only 'stable' intervals are used.  If needed, I can provide a very simple filter that works well.

I would vote for code simplicity (no separate code paths for primary/secondary monitors; DWM vs non-DWM) -- and just use the now() wake up time all the time.  Just make sure that thread is the highest possible priority (since it uses no cpu and is only waiting for vsync).

If the 30 us difference actually matters, that can be resolved (eliminated) by running the wake up times through an algorithm similar to the 'var HZ' object/algorithm used at vsynctester.com -- that algorithm is able to extract a very precise timebase and interval from a series of wake up times.  But I suspect this is a complication that is not needed?

An unknown is video cards that have variable vsync intervals.
stanisc/10: Also, regarding "use DWM on a secondary monitor too when it has the same refresh rate (as the primary monitor)" -- the problem is that Hz rates are never exact (thermal drift?) which would mean the two Hz would experience drift (an interference pattern).

The difference in the actual vsync wake up times for the two monitors is then not a constant value, but all over the place.

For example, one display could be a Hz of 60.002 and the other display could be a Hz of 60.003.  Every minute, the vsync time of one (relative to the other) shifts by 1 ms.

@11: Yeah, the exact interval shouldn't matter that much in case of D3D VSync. It is way more important in the case of software v-sync for smooth animation.

I agree that just using the "now" timestamp always and not relying on DWM is probably good enough. I am going to measure a difference in smoothness just to be sure.

Comment 14 Deleted

I've measured smoothness of frame timestamps reported by 3 methods:
- D3D v-sync using now() timestamp
- D3D v-sync using DWM (the current solution)
- Baseline - software v-sync

In the first case I've got CoVar of frame deltas = 0.27%
In the other two cases I've got CoVar of frame deltas = 0.16-0.18%. The baseline approach uses the same DWM code so it isn't surprising that the smoothness of frame timestamps is the same. So it appears the "now() timestamp" approach would regress the smoothness a bit. 

@12: I would get the monitor frequency from EnumDisplaySettings (dmDisplayFrequency member) which is an integer number that reflects the stated refresh rate rather than the actual one.
I've done more experimentation and realized that now matter how v-sync is generated there are still some configurations where multi-monitor synchronization doesn't work correctly.

1) If v-sync is aligned with the current monitor things don't work correctly on a secondary monitor that is running at a higher refresh rate than the primary monitor. In this case the main GPU thread ends up blocked in IDXGISwapChain::Present call. So the frame rate ends up being throttled to the DWM frame rate.

2) Similarly if v-sync is always aligned with the primary monitor (this is how the current software v-sync works on Windows 8+) there is an issue when running on a secondary monitor with a lower refresh rate. In this case the main GPU thread also gets blocked in IDXGISwapChain::Present and the frame rate is throttled to the slower monitor refresh rate. 

Either way it looks like IDXGISwapChain::Present is trying to ensure that the frame is visible for at least one v-blank interval.

So it appears we can't fix this without turning ANGLE synchronization off and doing that might result in some frames never hitting the screen.
stanisc/16: It would sure be nice to have a flag to control ANGLE vsync for more testing...

I suspect that what you will find out is that with a secondary monitor at a slower Hz than the Hz of the primary monitor, ANGLE vsync is key to keeping frames mostly properly synchronized -- due to a pipeline effect combined with a very reliable present time (a reliable interference pattern between the two Hz).  The moment the (relative) present time changes (which will happen with ANGLE vsync off), the interference pattern between the two Hz is no longer reliable and you will see a lot more vsync failures.

[the odd/even frame pre/post delay at vsynctester.com is a great way to expose and 'see' this effect in the vsync indicator]

The bottom line: in the very specific case of the secondary monitor at a slower Hz than the primary monitor, ANGLE vsync (a reliable interference pattern) is having a very beneficial effect on vsync (on the secondary monitor).
Project Member

Comment 18 by bugdroid1@chromium.org, May 17 2017

The following revision refers to this bug:
  https://chromium.googlesource.com/chromium/src.git/+/873b91f1c41220589c05e39a9424ffc99eab785a

commit 873b91f1c41220589c05e39a9424ffc99eab785a
Author: stanisc <stanisc@chromium.org>
Date: Wed May 17 01:08:31 2017

D3D V-sync: prevent timestamp drift on a secondary monitor

I got back some preliminary UMA data from Canary experiment that
confirm the timestamp drift relative to the timing of v-sync signal
which makes BeginImplFrameLatency2 UMA to be all over the place with
a distribution that is spread evenly in the entire 0 - 16667 range.

This happens because D3D V-sync signal is generated based on v-blank
event for a display that contains contains the window (the current
display), but the timestamp is obtained from DWM which is based on
the most recent v-blank timing for the primary monitor. So if a
secondary monitor frequency is even slightly different that causes
v-sync / RAF timestamp drift that is clearly visible on some websites
like vsynctester.com.

One possible solution is to capture the timestamp when v-blank event
is received, but that seems to be a bit less smooth than the DWM
timestamp. So the compromise is to use DWM timing only when running on
a primary monitor; otherwise use the v-blank wake-up timestamp.
I've verified that this fixes BeginImplFrameLatency2 UMA distribution on
my setup where the secondary monitor refresh rate seems to differ from
the primary monitor by about 0.15 Hz.

BUG=467617, 680639 
CQ_INCLUDE_TRYBOTS=master.tryserver.chromium.android:android_optional_gpu_tests_rel;master.tryserver.chromium.linux:linux_optional_gpu_tests_rel;master.tryserver.chromium.mac:mac_optional_gpu_tests_rel;master.tryserver.chromium.win:win_optional_gpu_tests_rel

Review-Url: https://codereview.chromium.org/2874833003
Cr-Commit-Position: refs/heads/master@{#472279}

[modify] https://crrev.com/873b91f1c41220589c05e39a9424ffc99eab785a/gpu/ipc/service/BUILD.gn
[modify] https://crrev.com/873b91f1c41220589c05e39a9424ffc99eab785a/gpu/ipc/service/gpu_vsync_provider_win.cc

Status: Fixed (was: Assigned)
Fixed for D3D V-sync, not fixing for timer based implementation.

Sign in to add a comment