Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(352)

Side by Side Diff: gpu/ipc/service/gpu_vsync_provider_win.cc

Issue 2874833003: D3D V-sync: prevent timestamp drift on a secondary monitor (Closed)
Patch Set: Merged and fixed a typo in a comment. Created 3 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « gpu/ipc/service/BUILD.gn ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2016 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "gpu/ipc/service/gpu_vsync_provider_win.h" 5 #include "gpu/ipc/service/gpu_vsync_provider_win.h"
6 6
7 #include <dwmapi.h>
8 #include <windows.h>
9
7 #include <string> 10 #include <string>
8 11
9 #include "base/atomicops.h" 12 #include "base/atomicops.h"
10 #include "base/debug/alias.h" 13 #include "base/debug/alias.h"
11 #include "base/strings/stringprintf.h" 14 #include "base/strings/stringprintf.h"
12 #include "base/threading/thread.h" 15 #include "base/threading/thread.h"
13 #include "base/trace_event/trace_event.h" 16 #include "base/trace_event/trace_event.h"
14 #include "gpu/ipc/common/gpu_messages.h" 17 #include "gpu/ipc/common/gpu_messages.h"
15 #include "ipc/ipc_message_macros.h" 18 #include "ipc/ipc_message_macros.h"
16 #include "ipc/message_filter.h" 19 #include "ipc/message_filter.h"
17 #include "ui/gl/vsync_provider_win.h"
18
19 #include <windows.h>
20 20
21 namespace gpu { 21 namespace gpu {
22 22
23 namespace { 23 namespace {
24 // Default interval used when no v-sync interval comes from DWM. 24 // Default v-sync interval used when there is no history of v-sync timestamps.
25 const int kDefaultTimerBasedInterval = 16666; 25 const int kDefaultInterval = 16666;
26 26
27 // from <D3dkmthk.h> 27 // from <D3dkmthk.h>
28 typedef LONG NTSTATUS; 28 typedef LONG NTSTATUS;
29 typedef UINT D3DKMT_HANDLE; 29 typedef UINT D3DKMT_HANDLE;
30 typedef UINT D3DDDI_VIDEO_PRESENT_SOURCE_ID; 30 typedef UINT D3DDDI_VIDEO_PRESENT_SOURCE_ID;
31 31
32 #define STATUS_SUCCESS ((NTSTATUS)0x00000000L) 32 #define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
33 #define STATUS_GRAPHICS_PRESENT_OCCLUDED ((NTSTATUS)0xC01E0006L) 33 #define STATUS_GRAPHICS_PRESENT_OCCLUDED ((NTSTATUS)0xC01E0006L)
34 34
35 typedef struct _D3DKMT_OPENADAPTERFROMHDC { 35 typedef struct _D3DKMT_OPENADAPTERFROMHDC {
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after
72 72
73 private: 73 private:
74 friend class base::RefCountedThreadSafe<GpuVSyncWorker>; 74 friend class base::RefCountedThreadSafe<GpuVSyncWorker>;
75 ~GpuVSyncWorker() override; 75 ~GpuVSyncWorker() override;
76 76
77 void Reschedule(); 77 void Reschedule();
78 bool OpenAdapter(const wchar_t* device_name); 78 bool OpenAdapter(const wchar_t* device_name);
79 void CloseAdapter(); 79 void CloseAdapter();
80 NTSTATUS WaitForVBlankEvent(); 80 NTSTATUS WaitForVBlankEvent();
81 81
82 void SendGpuVSyncUpdate(base::TimeTicks now, 82 void AddTimestamp(base::TimeTicks timestamp);
83 base::TimeTicks timestamp, 83 void AddInterval(base::TimeDelta interval);
84 base::TimeDelta interval); 84 base::TimeDelta GetAverageInterval() const;
85 void ClearIntervalHistory();
86
87 bool GetDisplayFrequency(const wchar_t* device_name, DWORD* frequency);
88 void UpdateCurrentDisplayFrequency();
89 bool GetDwmVBlankTimestamp(base::TimeTicks* timestamp);
90
91 void SendGpuVSyncUpdate(base::TimeTicks now, bool use_dwm);
92
85 void InvokeCallbackAndReschedule(base::TimeTicks timestamp, 93 void InvokeCallbackAndReschedule(base::TimeTicks timestamp,
86 base::TimeDelta interval); 94 base::TimeDelta interval);
87 void UseDelayBasedVSyncOnError(); 95 void UseDelayBasedVSyncOnError();
88 void ScheduleDelayBasedVSync(base::TimeTicks timebase,
89 base::TimeDelta interval);
90 96
91 // Specifies whether background tasks are running. 97 // Specifies whether background tasks are running.
92 // This can be set on background thread only. 98 // This can be set on background thread only.
93 bool running_ = false; 99 bool running_ = false;
94 100
95 // Specified whether the worker is enabled. This is accessed from both 101 // Specified whether the worker is enabled. This is accessed from both
96 // threads but can be changed on the main thread only. 102 // threads but can be changed on the main thread only.
97 base::subtle::AtomicWord enabled_ = false; 103 base::subtle::AtomicWord enabled_ = false;
98 104
99 const gfx::VSyncProvider::UpdateVSyncCallback callback_; 105 const gfx::VSyncProvider::UpdateVSyncCallback callback_;
100 const SurfaceHandle surface_handle_; 106 const SurfaceHandle surface_handle_;
101 107
102 // The actual timing and interval comes from the nested provider.
103 std::unique_ptr<gl::VSyncProviderWin> vsync_provider_;
104
105 PFND3DKMTOPENADAPTERFROMHDC open_adapter_from_hdc_ptr_; 108 PFND3DKMTOPENADAPTERFROMHDC open_adapter_from_hdc_ptr_;
106 PFND3DKMTCLOSEADAPTER close_adapter_ptr_; 109 PFND3DKMTCLOSEADAPTER close_adapter_ptr_;
107 PFND3DKMTWAITFORVERTICALBLANKEVENT wait_for_vertical_blank_event_ptr_; 110 PFND3DKMTWAITFORVERTICALBLANKEVENT wait_for_vertical_blank_event_ptr_;
108 111
109 std::wstring current_device_name_; 112 std::wstring current_device_name_;
110 D3DKMT_HANDLE current_adapter_handle_ = 0; 113 D3DKMT_HANDLE current_adapter_handle_ = 0;
111 D3DDDI_VIDEO_PRESENT_SOURCE_ID current_source_id_ = 0; 114 D3DDDI_VIDEO_PRESENT_SOURCE_ID current_source_id_ = 0;
115
116 // Last known v-sync timestamp.
117 base::TimeTicks last_timestamp_;
118
119 // Current display refresh frequency in Hz which is used to detect
120 // when the frequency changes and and to update the accepted interval
121 // range below.
122 DWORD current_display_frequency_ = 0;
123
124 // Range of intervals accepted for the average calculation which is
125 // +/-20% from the interval corresponding to the display frequency above.
126 // This is used to filter out outliers.
127 base::TimeDelta min_accepted_interval_;
128 base::TimeDelta max_accepted_interval_;
129
130 // History of recent deltas between timestamps which is used to calculate the
131 // average v-sync interval and organized as a circular buffer.
132 static const size_t kIntervalHistorySize = 60;
133 base::TimeDelta interval_history_[kIntervalHistorySize];
134 size_t history_index_ = 0;
135 size_t history_size_ = 0;
136
137 // Rolling sum of intervals in the circular buffer above.
138 base::TimeDelta rolling_interval_sum_;
112 }; 139 };
113 140
114 GpuVSyncWorker::GpuVSyncWorker( 141 GpuVSyncWorker::GpuVSyncWorker(
115 const gfx::VSyncProvider::UpdateVSyncCallback& callback, 142 const gfx::VSyncProvider::UpdateVSyncCallback& callback,
116 SurfaceHandle surface_handle) 143 SurfaceHandle surface_handle)
117 : base::Thread(base::StringPrintf("VSync-%d", surface_handle)), 144 : base::Thread(base::StringPrintf("VSync-%d", surface_handle)),
118 callback_(callback), 145 callback_(callback),
119 surface_handle_(surface_handle), 146 surface_handle_(surface_handle) {
120 vsync_provider_(new gl::VSyncProviderWin(surface_handle)) {
121 HMODULE gdi32 = GetModuleHandle(L"gdi32"); 147 HMODULE gdi32 = GetModuleHandle(L"gdi32");
122 if (!gdi32) { 148 if (!gdi32) {
123 NOTREACHED() << "Can't open gdi32.dll"; 149 NOTREACHED() << "Can't open gdi32.dll";
124 return; 150 return;
125 } 151 }
126 152
127 open_adapter_from_hdc_ptr_ = reinterpret_cast<PFND3DKMTOPENADAPTERFROMHDC>( 153 open_adapter_from_hdc_ptr_ = reinterpret_cast<PFND3DKMTOPENADAPTERFROMHDC>(
128 ::GetProcAddress(gdi32, "D3DKMTOpenAdapterFromHdc")); 154 ::GetProcAddress(gdi32, "D3DKMTOpenAdapterFromHdc"));
129 if (!open_adapter_from_hdc_ptr_) { 155 if (!open_adapter_from_hdc_ptr_) {
130 NOTREACHED() << "Can't find D3DKMTOpenAdapterFromHdc in gdi32.dll"; 156 NOTREACHED() << "Can't find D3DKMTOpenAdapterFromHdc in gdi32.dll";
(...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after
202 228
203 if (current_device_name_.compare(monitor_info.szDevice) != 0) { 229 if (current_device_name_.compare(monitor_info.szDevice) != 0) {
204 // Monitor changed. Close the current adapter handle and open a new one. 230 // Monitor changed. Close the current adapter handle and open a new one.
205 CloseAdapter(); 231 CloseAdapter();
206 if (!OpenAdapter(monitor_info.szDevice)) { 232 if (!OpenAdapter(monitor_info.szDevice)) {
207 UseDelayBasedVSyncOnError(); 233 UseDelayBasedVSyncOnError();
208 return; 234 return;
209 } 235 }
210 } 236 }
211 237
238 UpdateCurrentDisplayFrequency();
239
240 // Use DWM timing only when running on the primary monitor which DWM
241 // is synchronized with and only if we can get accurate high resulution
242 // timestamps.
243 bool use_dwm = (monitor_info.dwFlags & MONITORINFOF_PRIMARY) != 0 &&
244 base::TimeTicks::IsHighResolution();
245
212 NTSTATUS wait_result = WaitForVBlankEvent(); 246 NTSTATUS wait_result = WaitForVBlankEvent();
213 if (wait_result != STATUS_SUCCESS) { 247 if (wait_result != STATUS_SUCCESS) {
214 if (wait_result == STATUS_GRAPHICS_PRESENT_OCCLUDED) { 248 if (wait_result == STATUS_GRAPHICS_PRESENT_OCCLUDED) {
215 // This may be triggered by the monitor going into sleep. 249 // This may be triggered by the monitor going into sleep.
216 UseDelayBasedVSyncOnError(); 250 UseDelayBasedVSyncOnError();
217 return; 251 return;
218 } else { 252 } else {
219 base::debug::Alias(&wait_result); 253 base::debug::Alias(&wait_result);
220 CHECK(false); 254 CHECK(false);
221 } 255 }
222 } 256 }
223 257
224 vsync_provider_->GetVSyncParameters( 258 SendGpuVSyncUpdate(base::TimeTicks::Now(), use_dwm);
225 base::Bind(&GpuVSyncWorker::SendGpuVSyncUpdate, base::Unretained(this),
226 base::TimeTicks::Now()));
227 } 259 }
228 260
229 void GpuVSyncWorker::SendGpuVSyncUpdate(base::TimeTicks now, 261 void GpuVSyncWorker::AddTimestamp(base::TimeTicks timestamp) {
230 base::TimeTicks timestamp, 262 if (!last_timestamp_.is_null()) {
231 base::TimeDelta interval) { 263 AddInterval(timestamp - last_timestamp_);
264 }
265
266 last_timestamp_ = timestamp;
267 }
268
269 void GpuVSyncWorker::AddInterval(base::TimeDelta interval) {
270 if (interval < min_accepted_interval_ || interval > max_accepted_interval_)
271 return;
272
273 if (history_size_ == kIntervalHistorySize) {
274 rolling_interval_sum_ -= interval_history_[history_index_];
275 } else {
276 history_size_++;
277 }
278
279 interval_history_[history_index_] = interval;
280 rolling_interval_sum_ += interval;
281 history_index_ = (history_index_ + 1) % kIntervalHistorySize;
282 }
283
284 void GpuVSyncWorker::ClearIntervalHistory() {
285 last_timestamp_ = base::TimeTicks();
286 rolling_interval_sum_ = base::TimeDelta();
287 history_index_ = 0;
288 history_size_ = 0;
289 }
290
291 base::TimeDelta GpuVSyncWorker::GetAverageInterval() const {
292 return !rolling_interval_sum_.is_zero()
293 ? rolling_interval_sum_ / history_size_
294 : base::TimeDelta::FromMicroseconds(kDefaultInterval);
295 }
296
297 bool GpuVSyncWorker::GetDisplayFrequency(const wchar_t* device_name,
298 DWORD* frequency) {
299 DEVMODE display_info;
300 display_info.dmSize = sizeof(DEVMODE);
301 display_info.dmDriverExtra = 0;
302
303 BOOL result =
304 EnumDisplaySettings(device_name, ENUM_CURRENT_SETTINGS, &display_info);
305 if (result && display_info.dmDisplayFrequency > 1) {
306 *frequency = display_info.dmDisplayFrequency;
307 return true;
308 }
309
310 return false;
311 }
312
313 void GpuVSyncWorker::UpdateCurrentDisplayFrequency() {
314 DWORD frequency;
315 DCHECK(!current_device_name_.empty());
316 if (!GetDisplayFrequency(current_device_name_.c_str(), &frequency)) {
317 current_display_frequency_ = 0;
318 return;
319 }
320
321 if (frequency != current_display_frequency_) {
322 current_display_frequency_ = frequency;
323 base::TimeDelta interval = base::TimeDelta::FromMicroseconds(
324 base::Time::kMicrosecondsPerSecond / static_cast<double>(frequency));
325 ClearIntervalHistory();
326
327 min_accepted_interval_ = interval * 0.8;
328 max_accepted_interval_ = interval * 1.2;
329 AddInterval(interval);
330 }
331 }
332
333 bool GpuVSyncWorker::GetDwmVBlankTimestamp(base::TimeTicks* timestamp) {
334 DWM_TIMING_INFO timing_info;
335 timing_info.cbSize = sizeof(timing_info);
336 HRESULT result = DwmGetCompositionTimingInfo(nullptr, &timing_info);
337 if (result != S_OK)
338 return false;
339
340 *timestamp = base::TimeTicks::FromQPCValue(
341 static_cast<LONGLONG>(timing_info.qpcVBlank));
342 return true;
343 }
344
345 void GpuVSyncWorker::SendGpuVSyncUpdate(base::TimeTicks now, bool use_dwm) {
346 base::TimeTicks timestamp;
232 base::TimeDelta adjustment; 347 base::TimeDelta adjustment;
233 348
234 if (!(timestamp.is_null() || interval.is_zero())) { 349 if (use_dwm && GetDwmVBlankTimestamp(&timestamp)) {
235 // Timestamp comes from DwmGetCompositionTimingInfo and apparently it might 350 // Timestamp comes from DwmGetCompositionTimingInfo and apparently it might
236 // be up to 2-3 vsync cycles in the past or in the future. 351 // be up to 2-3 vsync cycles in the past or in the future.
237 // The adjustment formula was suggested here: 352 // The adjustment formula was suggested here:
238 // http://www.vsynctester.com/firefoxisbroken.html 353 // http://www.vsynctester.com/firefoxisbroken.html
354 base::TimeDelta interval = GetAverageInterval();
239 adjustment = 355 adjustment =
240 ((now - timestamp + interval / 8) % interval + interval) % interval - 356 ((now - timestamp + interval / 8) % interval + interval) % interval -
241 interval / 8; 357 interval / 8;
242 timestamp = now - adjustment; 358 timestamp = now - adjustment;
243 } else { 359 } else {
244 // DWM must be disabled. 360 // Not using DWM.
245 timestamp = now; 361 timestamp = now;
246 } 362 }
247 363
364 AddTimestamp(timestamp);
365
248 TRACE_EVENT1("gpu", "GpuVSyncWorker::SendGpuVSyncUpdate", "adjustment", 366 TRACE_EVENT1("gpu", "GpuVSyncWorker::SendGpuVSyncUpdate", "adjustment",
249 adjustment.ToInternalValue()); 367 adjustment.ToInternalValue());
250 368
251 InvokeCallbackAndReschedule(timestamp, interval); 369 DCHECK_GT(GetAverageInterval().InMillisecondsF(), 0);
370 InvokeCallbackAndReschedule(timestamp, GetAverageInterval());
252 } 371 }
253 372
254 void GpuVSyncWorker::InvokeCallbackAndReschedule(base::TimeTicks timestamp, 373 void GpuVSyncWorker::InvokeCallbackAndReschedule(base::TimeTicks timestamp,
255 base::TimeDelta interval) { 374 base::TimeDelta interval) {
256 // Send update and restart the task if still enabled. 375 // Send update and restart the task if still enabled.
257 if (base::subtle::NoBarrier_Load(&enabled_)) { 376 if (base::subtle::NoBarrier_Load(&enabled_)) {
258 callback_.Run(timestamp, interval); 377 callback_.Run(timestamp, interval);
259 task_runner()->PostTask(FROM_HERE, 378 task_runner()->PostTask(FROM_HERE,
260 base::Bind(&GpuVSyncWorker::WaitForVSyncOnThread, 379 base::Bind(&GpuVSyncWorker::WaitForVSyncOnThread,
261 base::Unretained(this))); 380 base::Unretained(this)));
262 } else { 381 } else {
263 running_ = false; 382 running_ = false;
383 // Clear last_timestamp_ to avoid a long interval when the worker restarts.
384 last_timestamp_ = base::TimeTicks();
264 } 385 }
265 } 386 }
266 387
267 void GpuVSyncWorker::UseDelayBasedVSyncOnError() { 388 void GpuVSyncWorker::UseDelayBasedVSyncOnError() {
268 // This is called in a case of an error. 389 // This is called in a case of an error.
269 // Use timer based mechanism as a backup for one v-sync cycle, start with 390 // Use timer based mechanism as a backup for one v-sync cycle, start with
270 // getting VSync parameters to determine timebase and interval. 391 // getting VSync parameters to determine timebase and interval.
271 // TODO(stanisc): Consider a slower v-sync rate in this particular case. 392 // TODO(stanisc): Consider a slower v-sync rate in this particular case.
272 vsync_provider_->GetVSyncParameters(base::Bind(
273 &GpuVSyncWorker::ScheduleDelayBasedVSync, base::Unretained(this)));
274 }
275 393
276 void GpuVSyncWorker::ScheduleDelayBasedVSync(base::TimeTicks timebase, 394 base::TimeTicks timebase;
277 base::TimeDelta interval) { 395 GetDwmVBlankTimestamp(&timebase);
278 // This is called only when WaitForVBlankEvent fails due to monitor going to
279 // sleep. Use a delay based v-sync as a back-up.
280 if (interval.is_zero()) {
281 interval = base::TimeDelta::FromMicroseconds(kDefaultTimerBasedInterval);
282 }
283 396
397 base::TimeDelta interval = GetAverageInterval();
284 base::TimeTicks now = base::TimeTicks::Now(); 398 base::TimeTicks now = base::TimeTicks::Now();
285 base::TimeTicks next_vsync = now.SnappedToNextTick(timebase, interval); 399 base::TimeTicks next_vsync = now.SnappedToNextTick(timebase, interval);
286 400
287 task_runner()->PostDelayedTask( 401 task_runner()->PostDelayedTask(
288 FROM_HERE, 402 FROM_HERE,
289 base::Bind(&GpuVSyncWorker::InvokeCallbackAndReschedule, 403 base::Bind(&GpuVSyncWorker::InvokeCallbackAndReschedule,
290 base::Unretained(this), next_vsync, interval), 404 base::Unretained(this), next_vsync, interval),
291 next_vsync - now); 405 next_vsync - now);
292 } 406 }
293 407
(...skipping 26 matching lines...) Expand all
320 void GpuVSyncWorker::CloseAdapter() { 434 void GpuVSyncWorker::CloseAdapter() {
321 if (current_adapter_handle_ != 0) { 435 if (current_adapter_handle_ != 0) {
322 D3DKMT_CLOSEADAPTER close_adapter_data; 436 D3DKMT_CLOSEADAPTER close_adapter_data;
323 close_adapter_data.hAdapter = current_adapter_handle_; 437 close_adapter_data.hAdapter = current_adapter_handle_;
324 438
325 NTSTATUS result = close_adapter_ptr_(&close_adapter_data); 439 NTSTATUS result = close_adapter_ptr_(&close_adapter_data);
326 CHECK(result == STATUS_SUCCESS); 440 CHECK(result == STATUS_SUCCESS);
327 441
328 current_adapter_handle_ = 0; 442 current_adapter_handle_ = 0;
329 current_device_name_.clear(); 443 current_device_name_.clear();
444
445 ClearIntervalHistory();
330 } 446 }
331 } 447 }
332 448
333 NTSTATUS GpuVSyncWorker::WaitForVBlankEvent() { 449 NTSTATUS GpuVSyncWorker::WaitForVBlankEvent() {
334 D3DKMT_WAITFORVERTICALBLANKEVENT wait_for_vertical_blank_event_data; 450 D3DKMT_WAITFORVERTICALBLANKEVENT wait_for_vertical_blank_event_data;
335 wait_for_vertical_blank_event_data.hAdapter = current_adapter_handle_; 451 wait_for_vertical_blank_event_data.hAdapter = current_adapter_handle_;
336 wait_for_vertical_blank_event_data.hDevice = 0; 452 wait_for_vertical_blank_event_data.hDevice = 0;
337 wait_for_vertical_blank_event_data.VidPnSourceId = current_source_id_; 453 wait_for_vertical_blank_event_data.VidPnSourceId = current_source_id_;
338 454
339 return wait_for_vertical_blank_event_ptr_( 455 return wait_for_vertical_blank_event_ptr_(
(...skipping 104 matching lines...) Expand 10 before | Expand all | Expand 10 after
444 void GpuVSyncProviderWin::OnVSync(base::TimeTicks timestamp, 560 void GpuVSyncProviderWin::OnVSync(base::TimeTicks timestamp,
445 base::TimeDelta interval) { 561 base::TimeDelta interval) {
446 DCHECK(vsync_worker_->BelongsToWorkerThread()); 562 DCHECK(vsync_worker_->BelongsToWorkerThread());
447 563
448 message_filter_->Send( 564 message_filter_->Send(
449 base::MakeUnique<GpuCommandBufferMsg_UpdateVSyncParameters>( 565 base::MakeUnique<GpuCommandBufferMsg_UpdateVSyncParameters>(
450 message_filter_->route_id(), timestamp, interval)); 566 message_filter_->route_id(), timestamp, interval));
451 } 567 }
452 568
453 } // namespace gpu 569 } // namespace gpu
OLDNEW
« no previous file with comments | « gpu/ipc/service/BUILD.gn ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698