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
|
#include "swapchain_monitor.hh"
#include "device_context.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::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.semaphore_signal.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;
this->is_monitor_processing.store(true, std::memory_order_relaxed);
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());
this->is_monitor_processing.store(false, std::memory_order_relaxed);
pending_signal.semaphore_signal.signal(this->device);
}
}
void SwapchainMonitor::notify_semaphore(
const SemaphoreSignal& semaphore_signal) {
auto lock = std::unique_lock{this->mutex};
// Signal immediately if reflex is off or it's a no-op submit.
if (!this->was_low_latency_requested) {
semaphore_signal.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 &&
!this->is_monitor_processing.load(std::memory_order_relaxed) &&
std::ranges::all_of(this->pending_frame_spans,
[](const auto& frame_span) {
if (!frame_span) {
return true;
}
return frame_span->has_completed();
})) {
semaphore_signal.signal(this->device);
this->pending_frame_spans.clear();
return;
}
this->pending_signals.emplace_back(PendingSignal{
.semaphore_signal = semaphore_signal,
.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
|