1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244
|
// 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
|