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
|
#include "swapchain_monitor.hh"
#include "device_context.hh"
#include "helper.hh"
#include <vulkan/vulkan_core.h>
#include <functional>
#include <mutex>
namespace low_latency {
SwapchainMonitor::SwapchainMonitor(const DeviceContext& device,
const bool was_low_latency_requested)
: device(device), was_low_latency_requested(was_low_latency_requested),
swapchain_worker(
std::bind_front(&SwapchainMonitor::do_swapchain_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::do_swapchain_monitor(const std::stop_token stoken) {
for (;;) {
auto lock = std::unique_lock{this->mutex};
this->cv.wait(lock, stoken,
[&]() { return !this->wakeup_semaphores.empty(); });
if (stoken.stop_requested()) {
// Small chance an application might need outstanding semaphores
// to be signalled if it's closing to avoid a hang.
break;
}
// Look for the latest submission and make sure it's completed.
if (!this->in_flight_submissions.empty()) {
const auto submission = this->in_flight_submissions.back();
this->in_flight_submissions.clear();
if (!submission->empty()) {
submission->back()->tail_handle->await_time();
}
}
// We might want to signal them all? In theory it's the same timeline
// semaphore so obviously it's redundant to signal them one by one. In
// almost all cases, there should just be one here anyway.
const auto wakeup_semaphore = this->wakeup_semaphores.back();
wakeup_semaphores.clear();
wakeup_semaphore.signal(this->device);
}
}
void SwapchainMonitor::update_params(
const bool was_low_latency_requested,
const std::chrono::milliseconds present_delay) {
const auto lock = std::scoped_lock{this->mutex};
this->was_low_latency_requested = was_low_latency_requested;
this->present_delay = present_delay;
}
void SwapchainMonitor::notify_semaphore(const VkSemaphore& timeline_semaphore,
const std::uint64_t& value) {
const auto lock = std::scoped_lock{this->mutex};
const auto wakeup_semaphore = WakeupSemaphore{
.timeline_semaphore = timeline_semaphore, .value = value};
// Signal immediately if low_latency isn't requested or if we have no
// outstanding work.
if (!this->was_low_latency_requested ||
this->in_flight_submissions.empty()) {
wakeup_semaphore.signal(this->device);
return;
}
this->wakeup_semaphores.emplace_back(timeline_semaphore, value);
this->cv.notify_one();
}
void SwapchainMonitor::notify_present(
const QueueContext::submissions_t& submissions) {
const auto lock = std::scoped_lock{this->mutex};
// Fast path where this work has already completed.
if (!this->wakeup_semaphores.empty() && !submissions->empty()) {
const auto& finished = submissions->back()->tail_handle->get_time();
if (finished.has_value()) {
this->wakeup_semaphores.back().signal(this->device);
this->wakeup_semaphores.clear();
return;
}
}
this->in_flight_submissions.emplace_back(submissions);
this->cv.notify_one();
}
void SwapchainMonitor::wait_until() {
// No reason to lock when using VK_AMD_anti_lag.
if (this->in_flight_submissions.empty()) {
return;
}
const auto last_submissions = this->in_flight_submissions.back();
this->in_flight_submissions.clear();
if (last_submissions->empty()) {
return;
}
last_submissions->back()->tail_handle->await_time();
}
} // namespace low_latency
|