| // Copyright 2015 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ui/gl/vsync_provider_win.h" |
| |
| #include <dwmapi.h> |
| |
| #include "base/numerics/safe_conversions.h" |
| #include "base/time/time.h" |
| #include "base/trace_event/trace_event.h" |
| #include "ui/display/win/display_config_helper.h" |
| #include "ui/gfx/native_widget_types.h" |
| |
| namespace gl { |
| |
| namespace { |
| |
| // Returns a timebase (TimeTicks) & interval (TimeDelta) pair representing last |
| // vBlank timing and display period (in microseconds) calculated using |
| // DwmGetCompositionTimingInfo. In cases of failure returns an interval of 0, |
| // timebase may be 0 in cases where High Resolution Timers are not in use even |
| // when interval is successfully set. |
| std::pair<base::TimeTicks, base::TimeDelta> TryGetVSyncParamsFromDwmCompInfo() { |
| base::TimeTicks timebase; |
| base::TimeDelta interval; |
| |
| // Query the DWM timing info first if available. This will provide the most |
| // precise values. |
| DWM_TIMING_INFO timing_info; |
| timing_info.cbSize = sizeof(timing_info); |
| HRESULT result = ::DwmGetCompositionTimingInfo(NULL, &timing_info); |
| // DwmGetCompositionTimingInfo returns qpcVBlank & qpcRefreshPeriod as type |
| // QPC_TIME, which is defined as ULONGLONG. In Chromium time, such as |
| // base::TimeDelta, is stored as type LONGLONG. In normal operating conditions |
| // we don't expect DwmGetCompositionTimingInfo to return values larger than |
| // LLONG_MAX because it is built upon Windows APIs which also treat time as |
| // type LONGLONG and on a typical system where the QPC interval is 100ns then |
| // a qpcVBlank time of LLONG_MAX would represent ~29K years. There are cases |
| // where we can encounter values greater than LLONG_MAX however (see |
| // https://crbug.com/1499654), so we want to protect against this by falling |
| // back to another interval querying method. |
| if (result == S_OK && timing_info.qpcVBlank <= LLONG_MAX && |
| timing_info.qpcRefreshPeriod <= LLONG_MAX) { |
| // Calculate an interval value using the rateRefresh numerator and |
| // denominator. |
| base::TimeDelta rate_interval; |
| if (timing_info.rateRefresh.uiDenominator > 0 && |
| timing_info.rateRefresh.uiNumerator > 0) { |
| // Swap the numerator/denominator to convert frequency to period. |
| rate_interval = base::Microseconds(timing_info.rateRefresh.uiDenominator * |
| base::Time::kMicrosecondsPerSecond / |
| timing_info.rateRefresh.uiNumerator); |
| } |
| |
| if (base::TimeTicks::IsHighResolution()) { |
| // qpcRefreshPeriod is very accurate but noisy, and must be used with |
| // a high resolution timebase to avoid frequently missing Vsync. |
| timebase = base::TimeTicks::FromQPCValue( |
| base::checked_cast<LONGLONG>(timing_info.qpcVBlank)); |
| interval = base::TimeDelta::FromQPCValue( |
| base::checked_cast<LONGLONG>(timing_info.qpcRefreshPeriod)); |
| // Check for interval values that are impossibly low. A 29 microsecond |
| // interval was seen (from a qpcRefreshPeriod of 60). |
| if (interval < base::Milliseconds(1)) { |
| interval = rate_interval; |
| } |
| // Check for the qpcRefreshPeriod interval being improbably small |
| // compared to the rateRefresh calculated interval, as another |
| // attempt at detecting driver bugs. |
| if (!rate_interval.is_zero() && interval < rate_interval / 2) { |
| interval = rate_interval; |
| } |
| } else { |
| // If FrameTime is not high resolution, we do not want to translate |
| // the QPC value provided by DWM into the low-resolution timebase, |
| // which would be error prone and jittery. As a fallback, we assume |
| // the timebase is zero and use rateRefresh, which may be rounded but |
| // isn't noisy like qpcRefreshPeriod, instead. The fact that we don't |
| // have a timebase here may lead to brief periods of jank when our |
| // scheduling becomes offset from the hardware vsync. |
| interval = rate_interval; |
| } |
| } |
| |
| return {timebase, interval}; |
| } |
| |
| // Returns a TimeDelta representing display period (in microseconds) calculated |
| // using QueryDisplayConfig. In cases of failure returns a TimeDelta of 0. |
| base::TimeDelta TryGetVsyncIntervalFromDisplayConfig( |
| gfx::AcceleratedWidget window) { |
| base::TimeDelta interval; |
| |
| HMONITOR monitor = |
| window ? ::MonitorFromWindow(window, MONITOR_DEFAULTTONEAREST) |
| : ::MonitorFromWindow(nullptr, MONITOR_DEFAULTTOPRIMARY); |
| |
| if (auto path_info = display::win::GetDisplayConfigPathInfo(monitor); |
| path_info) { |
| auto& refresh_rate = path_info->targetInfo.refreshRate; |
| if (refresh_rate.Denominator != 0 && refresh_rate.Numerator != 0) { |
| double micro_seconds = base::ClampDiv( |
| base::ClampMul(base::Time::kMicrosecondsPerSecond, |
| static_cast<double>(refresh_rate.Denominator)), |
| static_cast<double>(refresh_rate.Numerator)); |
| interval = base::Microseconds(base::ClampRound<int64_t>(micro_seconds)); |
| } |
| } |
| |
| return interval; |
| } |
| |
| // Returns a TimeDelta representing display period (in microseconds) calculated |
| // using EnumDisplaySettings. In cases of failure returns a TimeDelta of 0. |
| base::TimeDelta TryGetVSyncIntervalFromDisplaySettings( |
| gfx::AcceleratedWidget window) { |
| base::TimeDelta interval; |
| // When DWM compositing is active all displays are normalized to the |
| // refresh rate of the primary display, and won't composite any faster. |
| // If DWM compositing is disabled, though, we can use the refresh rates |
| // reported by each display, which will help systems that have mis-matched |
| // displays that run at different frequencies. |
| |
| // NOTE: The EnumDisplaySettings API does not support fractional display |
| // frequencies, e.g. a display operating at 29.97hz will report a |
| // frequency of 29hz. |
| HMONITOR monitor = ::MonitorFromWindow(window, MONITOR_DEFAULTTONEAREST); |
| MONITORINFOEX monitor_info; |
| monitor_info.cbSize = sizeof(MONITORINFOEX); |
| if (::GetMonitorInfo(monitor, &monitor_info)) { |
| DEVMODE display_info; |
| display_info.dmSize = sizeof(DEVMODE); |
| display_info.dmDriverExtra = 0; |
| if (::EnumDisplaySettings(monitor_info.szDevice, ENUM_CURRENT_SETTINGS, |
| &display_info) && |
| display_info.dmDisplayFrequency > 1) { |
| interval = base::Microseconds( |
| (1.0 / base::checked_cast<double>(display_info.dmDisplayFrequency)) * |
| base::Time::kMicrosecondsPerSecond); |
| } |
| } |
| |
| return interval; |
| } |
| |
| } // namespace |
| |
| VSyncProviderWin::VSyncProviderWin(gfx::AcceleratedWidget window) |
| : window_(window) { |
| } |
| |
| VSyncProviderWin::~VSyncProviderWin() {} |
| |
| // static |
| void VSyncProviderWin::InitializeOneOff() { |
| static bool initialized = false; |
| if (initialized) |
| return; |
| initialized = true; |
| |
| // Prewarm sandbox |
| ::LoadLibrary(L"dwmapi.dll"); |
| } |
| |
| void VSyncProviderWin::GetVSyncParameters(UpdateVSyncCallback callback) { |
| base::TimeTicks timebase; |
| base::TimeDelta interval; |
| if (GetVSyncParametersIfAvailable(&timebase, &interval)) |
| std::move(callback).Run(timebase, interval); |
| } |
| |
| bool VSyncProviderWin::GetVSyncParametersIfAvailable( |
| base::TimeTicks* out_timebase, |
| base::TimeDelta* out_interval) { |
| TRACE_EVENT0("gpu", "WinVSyncProvider::GetVSyncParameters"); |
| |
| // Prefer getting vsync parameters from DwmCompositionInfo in order to get a |
| // timebase. |
| auto [timebase, interval] = TryGetVSyncParamsFromDwmCompInfo(); |
| |
| // If DwmCompositionInfo wasn't available then prefer getting the interval |
| // from QueryDisplayConfig as it supports fractional refresh rates. |
| if (interval.is_zero()) { |
| interval = TryGetVsyncIntervalFromDisplayConfig(window_); |
| } |
| |
| if (interval.is_zero()) { |
| interval = TryGetVSyncIntervalFromDisplaySettings(window_); |
| } |
| |
| if (interval.is_zero()) { |
| return false; |
| } |
| |
| *out_timebase = timebase; |
| *out_interval = interval; |
| return true; |
| } |
| |
| // On Windows versions greater than WIN11_22H2 DWM will execute at the |
| // refresh rate of the fastest operating display, where previously it |
| // would always align with the primary monitor. As a result of this, some |
| // clients of VSyncProviderWin need a way to get an interval that still |
| // aligns with the Primary monitor (e.g. VSyncThreadWin uses primary monitor |
| // vblanks for its timings so needs to report intervals that align with that). |
| // GetVSyncIntervalIfAvailable prioritizes this case, but still allows for |
| // fallbacks to use DwmCompositionInfo or EnumDisplaySettings if needed. |
| bool VSyncProviderWin::GetVSyncIntervalIfAvailable( |
| base::TimeDelta* out_interval) { |
| TRACE_EVENT0("gpu", "WinVSyncProvider::GetVSyncIntervalIfAvailable"); |
| |
| // Prefer getting vsync parameters from QueryDisplayConfig in order to |
| // align with window_'s or Primary monitor's vblanks. |
| base::TimeDelta interval = TryGetVsyncIntervalFromDisplayConfig(window_); |
| |
| // If QueryDisplayConfig wasn't available then prefer DwmCompositionInfo |
| // as it supports fractional refresh rates. |
| if (interval.is_zero()) { |
| base::TimeTicks timebase; |
| std::tie(timebase, interval) = TryGetVSyncParamsFromDwmCompInfo(); |
| } |
| |
| if (interval.is_zero()) { |
| interval = TryGetVSyncIntervalFromDisplaySettings(window_); |
| } |
| |
| if (interval.is_zero()) { |
| return false; |
| } |
| |
| *out_interval = interval; |
| return true; |
| } |
| |
| bool VSyncProviderWin::SupportGetVSyncParametersIfAvailable() const { |
| return true; |
| } |
| |
| bool VSyncProviderWin::IsHWClock() const { |
| return true; |
| } |
| |
| } // namespace gl |