File: vsync_provider_win.cc

package info (click to toggle)
chromium 138.0.7204.183-1
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 6,071,908 kB
  • sloc: cpp: 34,937,088; ansic: 7,176,967; javascript: 4,110,704; python: 1,419,953; asm: 946,768; xml: 739,971; pascal: 187,324; sh: 89,623; perl: 88,663; objc: 79,944; sql: 50,304; cs: 41,786; fortran: 24,137; makefile: 21,806; php: 13,980; tcl: 13,166; yacc: 8,925; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (244 lines) | stat: -rw-r--r-- 9,567 bytes parent folder | download | duplicates (8)
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