GCC Code Coverage Report


ernie/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
123 / 141
87.2%
Branches:
72 / 134
53.7%
Functions:
15 / 20
75.0%

device/queue.cpp
Line Branch Exec Source
1 // SPDX-License-Identifier: BSD-3-Clause
2 /// @file queue.cpp
3 /// @brief Timeline semaphores, and submitting to a queue with synchronization2.
4 #include "ernie/device/queue.h"
5
6 #include "ernie/util/algorithm.h"
7 #include "ernie/util/on_failure.h"
8
9 #include <array>
10 #include <limits>
11
12 namespace ernie {
13 namespace {
14
15 78 VkSemaphore CreateTimeline(VkDevice device, const std::string& name, bool exportable) {
16 78 const VkExportSemaphoreCreateInfo exported{
17 .sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO,
18 .handleTypes = static_cast<VkExternalSemaphoreHandleTypeFlags>(kExternalSemaphoreType)};
19 78 const VkSemaphoreTypeCreateInfo timeline{.sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO,
20
1/2
✓ Branch 0 taken 78 times.
✗ Branch 1 not taken.
78 .pNext = exportable ? &exported : nullptr,
21 78 .semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE};
22 78 const VkSemaphoreCreateInfo info{.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
23 78 .pNext = &timeline};
24 78 VkSemaphore semaphore = VK_NULL_HANDLE;
25
2/4
✓ Branch 2 taken 78 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 78 times.
✗ Branch 6 not taken.
78 CheckVk(vkCreateSemaphore(device, &info, nullptr, &semaphore), "Creating " + name);
26 78 return semaphore;
27 }
28
29 } // namespace
30
31 1229 bool Ticket::Done() const {
32
4/4
✓ Branch 0 taken 1158 times.
✓ Branch 1 taken 71 times.
✓ Branch 3 taken 1033 times.
✓ Branch 4 taken 125 times.
1229 return timeline == nullptr || timeline->Reached() >= value;
33 }
34
35 430 void Ticket::Wait() const {
36
2/2
✓ Branch 0 taken 56 times.
✓ Branch 1 taken 374 times.
430 if (timeline == nullptr) {
37 56 return;
38 }
39 374 ERNIE_ZONE("Wait for the GPU");
40
1/2
✓ Branch 1 taken 374 times.
✗ Branch 2 not taken.
374 const VkSemaphore semaphore = timeline->Handle();
41 374 const VkSemaphoreWaitInfo wait{.sType = VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO,
42 .semaphoreCount = 1,
43 .pSemaphores = &semaphore,
44 374 .pValues = &value};
45
1/2
✓ Branch 1 taken 374 times.
✗ Branch 2 not taken.
374 CheckVk(vkWaitSemaphores(timeline->Device(), &wait, std::numeric_limits<std::uint64_t>::max()),
46
1/2
✓ Branch 2 taken 374 times.
✗ Branch 3 not taken.
374 [&] { return "Waiting on " + timeline->Name(); });
47 374 }
48
49 78 Timeline::Timeline(VkDevice device, std::string name, bool exportable)
50 78 : device_(device), name_(std::move(name)), exportable_(exportable),
51
1/2
✓ Branch 1 taken 78 times.
✗ Branch 2 not taken.
78 semaphore_(CreateTimeline(device_, name_, exportable)) {}
52
53 ✗ Timeline::Timeline(VkDevice device, std::string name, ExternalHandle handle)
54 ✗ : device_(device), name_(std::move(name)), exportable_(false),
55
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ semaphore_(CreateTimeline(device_, name_, false)) {
56
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ const OnFailure destroy([this] { vkDestroySemaphore(device_, semaphore_, nullptr); });
57
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ ImportSemaphore(device_, semaphore_, handle, name_);
58 ✗ }
59
60 78 Timeline::~Timeline() {
61 78 vkDestroySemaphore(device_, semaphore_, nullptr);
62 78 }
63
64 1429 std::uint64_t Timeline::Reached() const {
65 1429 std::uint64_t reached = 0;
66 1429 CheckVk(vkGetSemaphoreCounterValue(device_, semaphore_, &reached),
67 1429 [&] { return "Reading " + name_; });
68 1429 return reached;
69 }
70
71 ✗ void Timeline::Signal(std::uint64_t value) const {
72 ✗ const VkSemaphoreSignalInfo signal{
73 ✗ .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SIGNAL_INFO, .semaphore = semaphore_, .value = value};
74 ✗ CheckVk(vkSignalSemaphore(device_, &signal), [&] { return "Signalling " + name_; });
75 ✗ }
76
77 1 ExternalHandle Timeline::Export() const {
78
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1 if (!exportable_) {
79
1/2
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
2 throw DeviceError(DeviceErrorKind::Unsupported, name_ + " was not created exportable");
80 }
81 ✗ return ExportSemaphore(device_, semaphore_, name_);
82 }
83
84 77 Queue::Queue(VkDevice device, std::uint32_t family, std::string name, bool exportable)
85 77 : device_(device), family_(family), name_(std::move(name)),
86
3/6
✓ Branch 1 taken 77 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 77 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 77 times.
✗ Branch 8 not taken.
154 timeline_(device, name_ + "'s timeline", exportable) {
87
1/2
✓ Branch 1 taken 77 times.
✗ Branch 2 not taken.
77 vkGetDeviceQueue(device_, family_, 0, &queue_);
88 77 const VkCommandPoolCreateInfo pool{.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
89 .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
90 77 .queueFamilyIndex = family_};
91
2/4
✓ Branch 1 taken 77 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 77 times.
✗ Branch 5 not taken.
77 CheckVk(vkCreateCommandPool(device_, &pool, nullptr, &pool_),
92
2/4
✓ Branch 1 taken 77 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 77 times.
✗ Branch 5 not taken.
231 "Creating " + name_ + "'s command pool");
93 77 }
94
95 77 Queue::~Queue() {
96 154 Quietly([this] { Last().Wait(); });
97 77 vkDestroyCommandPool(device_, pool_, nullptr);
98 77 }
99
100 272 Ticket Queue::Submit(const std::function<void(VkCommandBuffer)>& record,
101 std::span<const Ticket> waits, BinarySemaphores binary) {
102 // One recording at a time: the command pool is not thread-safe.
103 272 const std::lock_guard recording(recording_);
104 272 std::vector<VkSemaphoreSubmitInfo> waitInfos;
105
2/2
✓ Branch 0 taken 155 times.
✓ Branch 1 taken 271 times.
426 for (const Ticket& wait : waits) {
106
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 150 times.
155 if (wait.timeline == nullptr) {
107 5 continue;
108 }
109 // Checked first, so the error does not depend on how quickly the other device runs.
110
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 149 times.
150 if (wait.timeline->Device() != device_) {
111 1 throw DeviceError(DeviceErrorKind::Failed,
112
2/4
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
✓ Branch 5 taken 1 time.
✗ Branch 6 not taken.
2 name_ + " cannot wait on another device's " + wait.timeline->Name());
113 }
114
3/4
✓ Branch 1 taken 149 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 74 times.
✓ Branch 4 taken 75 times.
149 if (wait.Done()) {
115 74 continue;
116 }
117 75 waitInfos.push_back({.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO,
118
1/2
✓ Branch 1 taken 75 times.
✗ Branch 2 not taken.
75 .semaphore = wait.timeline->Handle(),
119
1/2
✓ Branch 1 taken 75 times.
✗ Branch 2 not taken.
75 .value = wait.value,
120 .stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT});
121 }
122
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 271 times.
271 if (binary.wait != VK_NULL_HANDLE) {
123
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ waitInfos.push_back({.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO,
124 .semaphore = binary.wait,
125 .stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT});
126 }
127
128
1/2
✓ Branch 1 taken 271 times.
✗ Branch 2 not taken.
271 std::unique_lock lock(mutex_);
129
1/2
✓ Branch 1 taken 271 times.
✗ Branch 2 not taken.
271 Recycle();
130 271 VkCommandBuffer commands = VK_NULL_HANDLE;
131
2/2
✓ Branch 0 taken 135 times.
✓ Branch 1 taken 136 times.
271 if (free_.empty()) {
132 135 const VkCommandBufferAllocateInfo allocate{
133 .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
134 135 .commandPool = pool_,
135 .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
136 135 .commandBufferCount = 1};
137
2/4
✓ Branch 1 taken 135 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 135 times.
✗ Branch 5 not taken.
135 CheckVk(vkAllocateCommandBuffers(device_, &allocate, &commands),
138
2/4
✓ Branch 1 taken 135 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 135 times.
✗ Branch 5 not taken.
405 "Allocating " + name_ + "'s command buffer");
139 } else {
140 136 commands = free_.back();
141 136 free_.pop_back();
142 }
143
1/2
✓ Branch 1 taken 271 times.
✗ Branch 2 not taken.
271 lock.unlock();
144 // Recorded without the lock, so recording may ask this queue for tickets.
145
1/2
✓ Branch 1 taken 271 times.
✗ Branch 2 not taken.
271 CheckVk(vkResetCommandBuffer(commands, 0),
146
1/2
✓ Branch 1 taken 271 times.
✗ Branch 2 not taken.
271 [&] { return "Resetting " + name_ + "'s command buffer"; });
147 271 const VkCommandBufferBeginInfo begin{.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
148 .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT};
149 271 try {
150
2/4
✓ Branch 2 taken 271 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 271 times.
✗ Branch 6 not taken.
271 CheckVk(vkBeginCommandBuffer(commands, &begin), [&] { return "Recording on " + name_; });
151
2/2
✓ Branch 1 taken 268 times.
✓ Branch 2 taken 3 times.
271 record(commands);
152
2/4
✓ Branch 2 taken 268 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 268 times.
✗ Branch 6 not taken.
268 CheckVk(vkEndCommandBuffer(commands), [&] { return "Recording on " + name_; });
153 3 } catch (...) {
154 // Nothing was submitted: the command buffer goes back, to be reset on its next use.
155
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 lock.lock();
156
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 free_.push_back(commands);
157 3 throw;
158 3 }
159
160
1/2
✓ Branch 1 taken 268 times.
✗ Branch 2 not taken.
268 lock.lock();
161 268 const std::uint64_t value = submitted_ + 1;
162 268 const VkCommandBufferSubmitInfo commandInfo{
163 268 .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO, .commandBuffer = commands};
164 268 const std::array signals{
165 VkSemaphoreSubmitInfo{.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO,
166 268 .semaphore = timeline_.Handle(),
167 .value = value,
168 .stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT},
169 VkSemaphoreSubmitInfo{.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO,
170 268 .semaphore = binary.signal,
171
1/2
✓ Branch 0 taken 268 times.
✗ Branch 1 not taken.
268 .stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT}};
172
1/2
✓ Branch 0 taken 268 times.
✗ Branch 1 not taken.
268 const VkSubmitInfo2 submit{
173 .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2,
174 268 .waitSemaphoreInfoCount = static_cast<std::uint32_t>(waitInfos.size()),
175 268 .pWaitSemaphoreInfos = waitInfos.data(),
176 .commandBufferInfoCount = 1,
177 .pCommandBufferInfos = &commandInfo,
178 268 .signalSemaphoreInfoCount = binary.signal != VK_NULL_HANDLE ? 2u : 1u,
179
2/4
✓ Branch 0 taken 268 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 268 times.
✗ Branch 4 not taken.
536 .pSignalSemaphoreInfos = signals.data()};
180
1/2
✓ Branch 1 taken 268 times.
✗ Branch 2 not taken.
268 const VkResult submitted = vkQueueSubmit2(queue_, 1, &submit, VK_NULL_HANDLE);
181
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 268 times.
268 if (submitted < VK_SUCCESS) {
182
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ free_.push_back(commands);
183 }
184
2/4
✓ Branch 2 taken 268 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
271 CheckVk(submitted, [&] { return "Submitting to " + name_; });
185 268 submitted_ = value;
186
1/2
✓ Branch 1 taken 268 times.
✗ Branch 2 not taken.
268 inFlight_.emplace_back(commands, value);
187
1/2
✓ Branch 0 taken 268 times.
✗ Branch 1 not taken.
268 return timeline_.At(value);
188 272 }
189
190 1351 Ticket Queue::Last() const {
191 1351 const std::lock_guard lock(mutex_);
192
2/2
✓ Branch 0 taken 126 times.
✓ Branch 1 taken 1225 times.
1351 return submitted_ == 0 ? Ticket() : timeline_.At(submitted_);
193 1351 }
194
195 ✗ VkResult Queue::Present(const VkPresentInfoKHR& info) {
196 // Vulkan leaves a queue's host access for its users to synchronize.
197 ✗ const std::lock_guard lock(mutex_);
198
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ return vkQueuePresentKHR(queue_, &info);
199 ✗ }
200
201 3 void Queue::Borrow(const std::function<void(VkQueue)>& use) {
202 3 const std::lock_guard lock(mutex_);
203
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 use(queue_);
204 3 }
205
206 271 void Queue::Recycle() {
207 271 const std::uint64_t reached = timeline_.Reached();
208 271 std::erase_if(inFlight_, [&](const auto& entry) {
209
2/2
✓ Branch 0 taken 162 times.
✓ Branch 1 taken 231 times.
393 if (entry.second > reached) {
210 return false;
211 }
212 162 free_.push_back(entry.first);
213 162 return true;
214 });
215 271 }
216
217 } // namespace ernie
218