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
|
#include "swapchain_monitor.hh"
#include "device_context.hh"
#include "helper.hh"
#include <functional>
namespace low_latency {
SwapchainMonitor::SwapchainMonitor(const DeviceContext& device)
: device(device),
monitor_worker(std::bind_front(&SwapchainMonitor::do_monitor, this)) {}
SwapchainMonitor::~SwapchainMonitor() {}
void SwapchainMonitor::WakeupSemaphore::signal(
const DeviceContext& device) const {
const auto ssi =
VkSemaphoreSignalInfo{.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SIGNAL_INFO,
.semaphore = this->timeline_semaphore,
.value = this->value};
THROW_NOT_VKSUCCESS(device.vtable.SignalSemaphore(device.device, &ssi));
}
void SwapchainMonitor::update_params(const bool was_low_latency_requested,
const std::chrono::microseconds delay) {
const auto lock = std::scoped_lock{this->mutex};
this->was_low_latency_requested = was_low_latency_requested;
this->present_delay = delay;
}
void SwapchainMonitor::do_monitor(const std::stop_token stoken) {
for (;;) {
auto lock = std::unique_lock{this->mutex};
this->cv.wait(lock, stoken,
[&]() { return !this->pending_signals.empty(); });
// Stop only if we're stopped and we have nothing to signal.
if (stoken.stop_requested() && this->pending_signals.empty()) {
break;
}
// Grab the most recent semaphore. When work completes, signal it.
const auto pending_signal = std::move(this->pending_signals.front());
this->pending_signals.pop_front();
// If we're stopping, signal the semaphore and don't worry about work
// actually completing. But we MUST drain them, or we get a hang.
if (stoken.stop_requested()) {
pending_signal.wakeup_semaphore.signal(this->device);
continue;
}
// Grab mutex protected present delay before we sleep - doesn't matter
// if it's 'old'.
const auto delay = this->present_delay;
lock.unlock();
// Wait for work to complete.
for (const auto& frame_span : pending_signal.frame_spans) {
if (frame_span) {
frame_span->await_completed();
}
}
// Wait for possible need to delay the frame.
using namespace std::chrono;
if (delay != 0us && this->last_signal_time.has_value()) {
const auto last = this->last_signal_time.get();
std::this_thread::sleep_until(last + delay);
}
this->last_signal_time.set(std::chrono::steady_clock::now());
pending_signal.wakeup_semaphore.signal(this->device);
}
}
void SwapchainMonitor::notify_semaphore(const VkSemaphore& timeline_semaphore,
const std::uint64_t& value) {
auto lock = std::unique_lock{this->mutex};
const auto wakeup_semaphore = WakeupSemaphore{
.timeline_semaphore = timeline_semaphore, .value = value};
// Signal immediately if reflex is off or it's a no-op submit.
if (!this->was_low_latency_requested) {
wakeup_semaphore.signal(this->device);
return;
}
// Signal immediately if we don't need to worry about delaying the frame and
// we have no outstanding work.
using namespace std::chrono;
if (this->present_delay == 0us &&
std::ranges::all_of(this->pending_frame_spans,
[](const auto& frame_span) {
if (!frame_span) {
return true;
}
return frame_span->has_completed();
})) {
wakeup_semaphore.signal(this->device);
this->pending_frame_spans.clear();
return;
}
this->pending_signals.emplace_back(PendingSignal{
.wakeup_semaphore = wakeup_semaphore,
.frame_spans = std::move(this->pending_frame_spans),
});
this->pending_frame_spans.clear();
lock.unlock();
this->cv.notify_one();
}
void SwapchainMonitor::attach_work(
std::vector<std::unique_ptr<FrameSpan>> frame_spans) {
const auto lock = std::scoped_lock{this->mutex};
if (!this->was_low_latency_requested) {
return;
}
this->pending_frame_spans = std::move(frame_spans);
}
} // namespace low_latency
|