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
|
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/gl/vsync_thread_win.h"
#include "base/bind.h"
#include "base/logging.h"
#include "base/memory/singleton.h"
#include "base/stl_util.h"
#include "ui/gl/gl_angle_util_win.h"
#include "ui/gl/vsync_observer.h"
namespace gl {
namespace {
Microsoft::WRL::ComPtr<IDXGIOutput> DXGIOutputFromMonitor(
HMONITOR monitor,
const Microsoft::WRL::ComPtr<ID3D11Device>& d3d11_device) {
Microsoft::WRL::ComPtr<IDXGIDevice> dxgi_device;
if (FAILED(d3d11_device.As(&dxgi_device))) {
DLOG(ERROR) << "Failed to retrieve DXGI device";
return nullptr;
}
Microsoft::WRL::ComPtr<IDXGIAdapter> dxgi_adapter;
if (FAILED(dxgi_device->GetAdapter(&dxgi_adapter))) {
DLOG(ERROR) << "Failed to retrieve DXGI adapter";
return nullptr;
}
size_t i = 0;
while (true) {
Microsoft::WRL::ComPtr<IDXGIOutput> output;
if (FAILED(dxgi_adapter->EnumOutputs(i++, &output)))
break;
DXGI_OUTPUT_DESC desc = {};
if (FAILED(output->GetDesc(&desc))) {
DLOG(ERROR) << "DXGI output GetDesc failed";
return nullptr;
}
if (desc.Monitor == monitor)
return output;
}
return nullptr;
}
} // namespace
// static
VSyncThreadWin* VSyncThreadWin::GetInstance() {
return base::Singleton<VSyncThreadWin>::get();
}
VSyncThreadWin::VSyncThreadWin()
: vsync_thread_("GpuVSyncThread"),
vsync_provider_(gfx::kNullAcceleratedWidget),
d3d11_device_(QueryD3D11DeviceObjectFromANGLE()) {
DCHECK(d3d11_device_);
base::Thread::Options options;
options.priority = base::ThreadPriority::DISPLAY;
vsync_thread_.StartWithOptions(std::move(options));
}
VSyncThreadWin::~VSyncThreadWin() {
{
base::AutoLock auto_lock(lock_);
observers_.clear();
}
vsync_thread_.Stop();
}
void VSyncThreadWin::AddObserver(VSyncObserver* obs) {
base::AutoLock auto_lock(lock_);
observers_.insert(obs);
if (is_idle_) {
is_idle_ = false;
vsync_thread_.task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&VSyncThreadWin::WaitForVSync, base::Unretained(this)));
}
}
void VSyncThreadWin::RemoveObserver(VSyncObserver* obs) {
base::AutoLock auto_lock(lock_);
observers_.erase(obs);
}
void VSyncThreadWin::WaitForVSync() {
base::TimeTicks vsync_phase;
base::TimeDelta vsync_interval;
const bool get_vsync_params_succeeded =
vsync_provider_.GetVSyncParametersIfAvailable(&vsync_phase,
&vsync_interval);
DCHECK(get_vsync_params_succeeded);
// From Raymond Chen's blog "How do I get a handle to the primary monitor?"
// https://devblogs.microsoft.com/oldnewthing/20141106-00/?p=43683
const HMONITOR monitor = MonitorFromWindow(nullptr, MONITOR_DEFAULTTOPRIMARY);
if (primary_monitor_ != monitor) {
primary_monitor_ = monitor;
primary_output_ = DXGIOutputFromMonitor(monitor, d3d11_device_);
}
const base::TimeTicks wait_for_vblank_start_time = base::TimeTicks::Now();
const bool wait_for_vblank_succeeded =
primary_output_ && SUCCEEDED(primary_output_->WaitForVBlank());
// WaitForVBlank returns very early instead of waiting until vblank when the
// monitor goes to sleep. We use 1ms as a threshold for the duration of
// WaitForVBlank and fallback to Sleep() if it returns before that. This
// could happen during normal operation for the first call after the vsync
// thread becomes non-idle, but it shouldn't happen often.
constexpr auto kVBlankIntervalThreshold =
base::TimeDelta::FromMilliseconds(1);
const base::TimeDelta wait_for_vblank_elapsed_time =
base::TimeTicks::Now() - wait_for_vblank_start_time;
if (!wait_for_vblank_succeeded ||
wait_for_vblank_elapsed_time < kVBlankIntervalThreshold) {
Sleep(static_cast<DWORD>(vsync_interval.InMillisecondsRoundedUp()));
}
base::AutoLock auto_lock(lock_);
if (!observers_.empty()) {
vsync_thread_.task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&VSyncThreadWin::WaitForVSync, base::Unretained(this)));
const base::TimeTicks vsync_time = base::TimeTicks::Now();
for (auto* obs : observers_)
obs->OnVSync(vsync_time, vsync_interval);
} else {
is_idle_ = true;
}
}
} // namespace gl
|