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 | ✗ | 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 | 11 | if (volkInitialize() != VK_SUCCESS) { | |
| 41 | ✗ | return "no Vulkan loader was found"; | |
| 42 | } | ||
| 43 | 11 | Api api; | |
| 44 | 11 | api.instanceVersion = DecodeVersion(volkGetInstanceVersion()); | |
| 45 | 11 | if (api.instanceVersion < kMinimumInstanceVersion) { | |
| 46 | ✗ | return TooOld("the Vulkan loader", api.instanceVersion, kMinimumInstanceVersion); | |
| 47 | } | ||
| 48 | 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 | 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 | ✗ | 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 | 1651143 | if (result == VK_ERROR_DEVICE_LOST) { | |
| 100 | ✗ | throw DeviceLostError("{} failed: the device was lost (VK_ERROR_DEVICE_LOST)", what); | |
| 101 | } | ||
| 102 | 1651143 | if (result != VK_SUCCESS) { | |
| 103 | ✗ | 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 | 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 | 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 | ✗ | g_validationMessageCount.fetch_add(1, std::memory_order_relaxed); | |
| 131 | ✗ | const LogLevel level = severity >= VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT | |
| 132 | ✗ | ? 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 | 13 | if (!validation) { | |
| 153 | 13 | return {}; | |
| 154 | } | ||
| 155 | ✗ | 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 | ✗ | 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 | 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 | 13 | info.enabledLayerCount = uint32_t(layers.size()); | |
| 182 | 13 | info.ppEnabledLayerNames = layers.data(); | |
| 183 | 13 | std::vector<const char*> extensions; | |
| 184 | 13 | if (available.Has(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME)) { | |
| 185 | 13 | info.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; | |
| 186 | 13 | extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); | |
| 187 | } | ||
| 188 | 13 | if (debugUtils) { | |
| 189 | 13 | extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); | |
| 190 | } | ||
| 191 | 91 | for (const char* surface : kSurfaceExtensions) { | |
| 192 | 78 | if (available.Has(surface)) { | |
| 193 | 52 | extensions.push_back(surface); | |
| 194 | } | ||
| 195 | } | ||
| 196 | 13 | info.enabledExtensionCount = uint32_t(extensions.size()); | |
| 197 | 13 | info.ppEnabledExtensionNames = extensions.data(); | |
| 198 | 13 | VkInstance instance = VK_NULL_HANDLE; | |
| 199 | 13 | if (creator != nullptr) { | |
| 200 | 2 | instance = creator->createInstance(vkGetInstanceProcAddr, info); | |
| 201 | 2 | if (instance == VK_NULL_HANDLE) { | |
| 202 | ✗ | throw Error("the Vulkan creator made no instance"); | |
| 203 | } | ||
| 204 | } else { | ||
| 205 | 11 | Check(vkCreateInstance(&info, nullptr, &instance), "vkCreateInstance"); | |
| 206 | 11 | volkLoadInstanceOnly(instance); | |
| 207 | } | ||
| 208 | |||
| 209 | 13 | if (!layers.empty()) { | |
| 210 | ✗ | 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 | 62 | VkInstance& instance = instances[validation ? 1 : 0]; | |
| 222 | 62 | if (instance == VK_NULL_HANDLE) { | |
| 223 | 11 | VkDebugUtilsMessengerEXT messenger = VK_NULL_HANDLE; | |
| 224 | 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 | 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 | 106 | if (m_messenger != VK_NULL_HANDLE) { | |
| 248 | ✗ | vkDestroyDebugUtilsMessengerEXT(m_instance, m_messenger, nullptr); | |
| 249 | ✗ | m_messenger = VK_NULL_HANDLE; | |
| 250 | } | ||
| 251 | 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 | if (!creator.createInstance || !creator.createDevice) { | |
| 259 | 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 | 20 | volkLoadDeviceTable(&functions, device); | |
| 284 | 20 | } | |
| 285 | |||
| 286 | } | ||
| 287 |