chrono_batch_render/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
107 / 172
62.2%
Branches:
60 / 129
46.5%
Functions:
19 / 25
76.0%

device/vulkan/api.cpp
Line Branch Exec Source
1 // SPDX-License-Identifier: BSD-3-Clause
2 #include "chrono_batch_render/device/vulkan/api.h"
3
4 #include <algorithm>
5 #include <atomic>
6 #include <cstring>
7 #include <mutex>
8 #include <string>
9 #include <utility>
10 #include <variant>
11
12 #include <volk.h>
13
14 #include "chrono_batch_render/device/vulkan/native.h"
15 #include "chrono_batch_render/util/error.h"
16 #include "chrono_batch_render/util/library.h"
17 #include "chrono_batch_render/util/log.h"
18 #include "chrono_batch_render/util/version.h"
19
20 namespace chrono::batch_render::device::vulkan {
21 namespace {
22
23 constexpr const char* kValidationLayer = "VK_LAYER_KHRONOS_validation";
24
25 std::mutex g_volkMutex;
26 std::atomic<bool> g_validationEnabled{false};
27 std::atomic<uint64_t> g_validationMessageCount{0};
28
29 ✗ bool HasLayer(const char* name) {
30 ✗ const std::vector<VkLayerProperties> layers =
31 ✗ Enumerate<VkLayerProperties>(vkEnumerateInstanceLayerProperties);
32 ✗ return std::any_of(layers.begin(), layers.end(), [name](const VkLayerProperties& layer) {
33
0/14
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
✗ return std::strcmp(layer.layerName, name) == 0;
34 ✗ });
35 ✗ }
36
37 static_assert(DecodeVersion(VK_MAKE_API_VERSION(0, 1, 4, 341)) == Version{1, 4, 341});
38
39 11 Loaded<Api> Load() {
40
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 11 times.
11 if (volkInitialize() != VK_SUCCESS) {
41 ✗ return "no Vulkan loader was found";
42 }
43 11 Api api;
44
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 11 times.
11 api.instanceVersion = DecodeVersion(volkGetInstanceVersion());
45
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 11 times.
11 if (api.instanceVersion < kMinimumInstanceVersion) {
46 ✗ return TooOld("the Vulkan loader", api.instanceVersion, kMinimumInstanceVersion);
47 }
48
1/1
✓ Branch 2 taken 11 times.
11 Log(LogLevel::Debug, "the Vulkan loader supports Vulkan {}", api.instanceVersion.ToString());
49 11 return api;
50 }
51
52 62 const Loaded<Api>& Cached() {
53
5/6
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 51 times.
✓ Branch 3 taken 11 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 11 times.
✓ Branch 9 taken 11 times.
62 static const Loaded<Api> loaded = LogUnavailable("Vulkan", Load());
54 62 return loaded;
55 }
56
57 }
58
59 ✗ bool Available() {
60 ✗ return std::holds_alternative<Api>(Cached());
61 }
62
63 62 const Api& GetApi() {
64 62 return RequireLoaded(Cached());
65 }
66
67 ✗ const char* ResultName(VkResult result) {
68
0/21
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
✗ Branch 20 not taken.
✗ switch (result) {
69 #define CHRONO_BATCH_RENDER_RESULT(name) \
70 case name: \
71 return #name;
72 CHRONO_BATCH_RENDER_RESULT(VK_SUCCESS)
73 ✗ CHRONO_BATCH_RENDER_RESULT(VK_NOT_READY)
74 ✗ CHRONO_BATCH_RENDER_RESULT(VK_TIMEOUT)
75 ✗ CHRONO_BATCH_RENDER_RESULT(VK_INCOMPLETE)
76 ✗ CHRONO_BATCH_RENDER_RESULT(VK_ERROR_OUT_OF_HOST_MEMORY)
77 ✗ CHRONO_BATCH_RENDER_RESULT(VK_ERROR_OUT_OF_DEVICE_MEMORY)
78 ✗ CHRONO_BATCH_RENDER_RESULT(VK_ERROR_INITIALIZATION_FAILED)
79 ✗ CHRONO_BATCH_RENDER_RESULT(VK_ERROR_DEVICE_LOST)
80 ✗ CHRONO_BATCH_RENDER_RESULT(VK_ERROR_MEMORY_MAP_FAILED)
81 ✗ CHRONO_BATCH_RENDER_RESULT(VK_ERROR_LAYER_NOT_PRESENT)
82 ✗ CHRONO_BATCH_RENDER_RESULT(VK_ERROR_EXTENSION_NOT_PRESENT)
83 ✗ CHRONO_BATCH_RENDER_RESULT(VK_ERROR_FEATURE_NOT_PRESENT)
84 ✗ CHRONO_BATCH_RENDER_RESULT(VK_ERROR_INCOMPATIBLE_DRIVER)
85 ✗ CHRONO_BATCH_RENDER_RESULT(VK_ERROR_TOO_MANY_OBJECTS)
86 ✗ CHRONO_BATCH_RENDER_RESULT(VK_ERROR_FORMAT_NOT_SUPPORTED)
87 ✗ CHRONO_BATCH_RENDER_RESULT(VK_ERROR_FRAGMENTED_POOL)
88 ✗ CHRONO_BATCH_RENDER_RESULT(VK_ERROR_OUT_OF_POOL_MEMORY)
89 ✗ CHRONO_BATCH_RENDER_RESULT(VK_ERROR_INVALID_EXTERNAL_HANDLE)
90 ✗ CHRONO_BATCH_RENDER_RESULT(VK_ERROR_FRAGMENTATION)
91 ✗ CHRONO_BATCH_RENDER_RESULT(VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS)
92 #undef CHRONO_BATCH_RENDER_RESULT
93 ✗ default:
94 ✗ return "VK_ERROR_UNKNOWN";
95 }
96 }
97
98 1651143 void Check(VkResult result, const char* what) {
99
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1651143 times.
1651143 if (result == VK_ERROR_DEVICE_LOST) {
100
0/1
✗ Branch 2 not taken.
✗ throw DeviceLostError("{} failed: the device was lost (VK_ERROR_DEVICE_LOST)", what);
101 }
102
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1651143 times.
1651143 if (result != VK_SUCCESS) {
103
0/3
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ Branch 8 not taken.
✗ throw ApiError(what, ResultName(result), result);
104 }
105 1651143 }
106
107 137 Extensions::Extensions(VkPhysicalDevice device)
108 137 : m_extensions(
109 274 Enumerate<VkExtensionProperties>([device](uint32_t* count, VkExtensionProperties* items) {
110
2/2
✓ Branch 0 taken 124 times.
✓ Branch 1 taken 150 times.
274 if (device != VK_NULL_HANDLE) {
111 124 vkEnumerateDeviceExtensionProperties(device, nullptr, count, items);
112 } else {
113 150 vkEnumerateInstanceExtensionProperties(nullptr, count, items);
114 }
115 411 })) {}
116
117 728 bool Extensions::Has(const char* name) const {
118 728 return std::any_of(m_extensions.begin(), m_extensions.end(),
119 36693 [name](const VkExtensionProperties& extension) {
120
11/14
✓ Branch 0 taken 88 times.
✓ Branch 1 taken 9227 times.
✓ Branch 2 taken 117 times.
✓ Branch 3 taken 9110 times.
✓ Branch 4 taken 236 times.
✓ Branch 5 taken 8874 times.
✓ Branch 6 taken 146 times.
✓ Branch 7 taken 8728 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✓ Branch 10 taken 13 times.
✓ Branch 11 taken 26 times.
✗ Branch 12 not taken.
✓ Branch 13 taken 128 times.
9482 return std::strcmp(extension.extensionName, name) == 0;
121 728 });
122 }
123
124 namespace {
125
126 ✗ VKAPI_ATTR VkBool32 VKAPI_CALL ReportValidation(VkDebugUtilsMessageSeverityFlagBitsEXT severity,
127 VkDebugUtilsMessageTypeFlagsEXT,
128 const VkDebugUtilsMessengerCallbackDataEXT* data,
129 void*) {
130
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ g_validationMessageCount.fetch_add(1, std::memory_order_relaxed);
131 ✗ const LogLevel level = severity >= VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT
132
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ ? LogLevel::Error
133 : LogLevel::Warning;
134 ✗ Log(level, "Vulkan validation: {}", data->pMessage);
135 ✗ return VK_FALSE;
136 }
137
138 ✗ VkDebugUtilsMessengerEXT CreateValidationMessenger(VkInstance instance) {
139 ✗ VkDebugUtilsMessengerCreateInfoEXT info{
140 VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT};
141 ✗ info.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
142 VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
143 ✗ info.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT;
144 ✗ info.pfnUserCallback = ReportValidation;
145 ✗ VkDebugUtilsMessengerEXT messenger = VK_NULL_HANDLE;
146 ✗ Check(vkCreateDebugUtilsMessengerEXT(instance, &info, nullptr, &messenger),
147 "vkCreateDebugUtilsMessengerEXT");
148 ✗ return messenger;
149 }
150
151 13 std::vector<const char*> ValidationLayers(bool validation, bool debugUtils) {
152
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
13 if (!validation) {
153 13 return {};
154 }
155
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (!debugUtils) {
156 ✗ Log(LogLevel::Warning, "validation was asked for, but {} is not available",
157 VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
158 ✗ return {};
159 }
160
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ if (!HasLayer(kValidationLayer)) {
161 ✗ Log(LogLevel::Warning, "validation was asked for, but {} is not installed",
162 kValidationLayer);
163 ✗ return {};
164 }
165 ✗ Log(LogLevel::Debug, "validating Vulkan calls with {}", kValidationLayer);
166 ✗ return {kValidationLayer};
167 }
168
169 13 VkInstance CreateInstance(bool validation, const Creator* creator,
170 VkDebugUtilsMessengerEXT& messenger) {
171 13 VkApplicationInfo application{VK_STRUCTURE_TYPE_APPLICATION_INFO};
172 13 application.pApplicationName = "Chrono Batch Render";
173 13 application.pEngineName = "Chrono Batch Render";
174 13 application.apiVersion = VK_API_VERSION_1_3;
175 13 const Extensions available;
176 13 const bool debugUtils = available.Has(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
177
1/1
✓ Branch 1 taken 13 times.
13 const std::vector<const char*> layers = ValidationLayers(validation, debugUtils);
178
179 13 VkInstanceCreateInfo info{VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO};
180 13 info.pApplicationInfo = &application;
181
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
13 info.enabledLayerCount = uint32_t(layers.size());
182 13 info.ppEnabledLayerNames = layers.data();
183 13 std::vector<const char*> extensions;
184
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
13 if (available.Has(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME)) {
185 13 info.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
186
1/1
✓ Branch 1 taken 13 times.
13 extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME);
187 }
188
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
13 if (debugUtils) {
189
1/1
✓ Branch 1 taken 13 times.
13 extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
190 }
191
2/2
✓ Branch 0 taken 78 times.
✓ Branch 1 taken 13 times.
91 for (const char* surface : kSurfaceExtensions) {
192
2/2
✓ Branch 0 taken 52 times.
✓ Branch 1 taken 26 times.
78 if (available.Has(surface)) {
193
1/1
✓ Branch 1 taken 52 times.
52 extensions.push_back(surface);
194 }
195 }
196
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 11 times.
13 info.enabledExtensionCount = uint32_t(extensions.size());
197 13 info.ppEnabledExtensionNames = extensions.data();
198 13 VkInstance instance = VK_NULL_HANDLE;
199
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 11 times.
13 if (creator != nullptr) {
200
1/1
✓ Branch 1 taken 2 times.
2 instance = creator->createInstance(vkGetInstanceProcAddr, info);
201
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (instance == VK_NULL_HANDLE) {
202
0/2
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ throw Error("the Vulkan creator made no instance");
203 }
204 } else {
205
2/2
✓ Branch 1 taken 11 times.
✓ Branch 4 taken 11 times.
11 Check(vkCreateInstance(&info, nullptr, &instance), "vkCreateInstance");
206
1/1
✓ Branch 1 taken 11 times.
11 volkLoadInstanceOnly(instance);
207 }
208
209
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 13 times.
13 if (!layers.empty()) {
210
0/1
✗ Branch 1 not taken.
✗ messenger = CreateValidationMessenger(instance);
211 ✗ g_validationEnabled.store(true, std::memory_order_relaxed);
212 }
213 26 return instance;
214 13 }
215
216 }
217
218 62 VkInstance Instance(bool validation) {
219 62 static VkInstance instances[2] = {};
220 62 const std::lock_guard lock(g_volkMutex);
221
1/2
✓ Branch 0 taken 62 times.
✗ Branch 1 not taken.
62 VkInstance& instance = instances[validation ? 1 : 0];
222
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 51 times.
62 if (instance == VK_NULL_HANDLE) {
223 11 VkDebugUtilsMessengerEXT messenger = VK_NULL_HANDLE;
224
1/1
✓ Branch 1 taken 11 times.
11 instance = CreateInstance(validation, nullptr, messenger);
225 }
226 62 return instance;
227 62 }
228
229 22 OwnedInstance::OwnedInstance(OwnedInstance&& other) noexcept
230 22 : m_instance(std::exchange(other.m_instance, VK_NULL_HANDLE)),
231 22 m_messenger(std::exchange(other.m_messenger, VK_NULL_HANDLE)) {}
232
233 22 OwnedInstance& OwnedInstance::operator=(OwnedInstance&& other) noexcept {
234
1/2
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
22 if (this != &other) {
235 22 Destroy();
236 22 m_instance = std::exchange(other.m_instance, VK_NULL_HANDLE);
237 22 m_messenger = std::exchange(other.m_messenger, VK_NULL_HANDLE);
238 }
239 22 return *this;
240 }
241
242 84 OwnedInstance::~OwnedInstance() {
243 84 Destroy();
244 84 }
245
246 106 void OwnedInstance::Destroy() noexcept {
247
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 106 times.
106 if (m_messenger != VK_NULL_HANDLE) {
248 ✗ vkDestroyDebugUtilsMessengerEXT(m_instance, m_messenger, nullptr);
249 ✗ m_messenger = VK_NULL_HANDLE;
250 }
251
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 104 times.
106 if (m_instance != VK_NULL_HANDLE) {
252 2 vkDestroyInstance(m_instance, nullptr);
253 2 m_instance = VK_NULL_HANDLE;
254 }
255 106 }
256
257 3 OwnedInstance CreateInstance(bool validation, const Creator& creator) {
258
3/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 2 times.
3 if (!creator.createInstance || !creator.createDevice) {
259
2/2
✓ Branch 2 taken 1 time.
✓ Branch 5 taken 1 time.
2 throw Error("a Vulkan creator needs both createInstance and createDevice");
260 }
261 2 Instance(false);
262 2 VkDebugUtilsMessengerEXT messenger = VK_NULL_HANDLE;
263 2 const VkInstance instance = CreateInstance(validation, &creator, messenger);
264 2 return {instance, messenger};
265 }
266
267 ✗ bool ValidationEnabled() {
268 ✗ return g_validationEnabled.load(std::memory_order_relaxed);
269 }
270
271 9 uint64_t ValidationMessageCount() {
272 9 return g_validationMessageCount.load(std::memory_order_relaxed);
273 }
274
275 62 std::vector<VkPhysicalDevice> PhysicalDevices(VkInstance instance) {
276 186 return Enumerate<VkPhysicalDevice>([instance](uint32_t* count, VkPhysicalDevice* devices) {
277 124 Check(vkEnumeratePhysicalDevices(instance, count, devices), "vkEnumeratePhysicalDevices");
278 62 });
279 }
280
281 20 void LoadDeviceFunctions(VolkDeviceTable& functions, VkDevice device) {
282 20 std::lock_guard lock(g_volkMutex);
283
1/1
✓ Branch 1 taken 20 times.
20 volkLoadDeviceTable(&functions, device);
284 20 }
285
286 }
287