1 | // Copyright (C) 2017 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "qbasicvulkanplatforminstance_p.h" |
5 | #include <QCoreApplication> |
6 | #include <QList> |
7 | #include <QLoggingCategory> |
8 | #include <QVarLengthArray> |
9 | |
10 | QT_BEGIN_NAMESPACE |
11 | |
12 | Q_LOGGING_CATEGORY(lcPlatVk, "qt.vulkan" ) |
13 | |
14 | /*! |
15 | \class QBasicPlatformVulkanInstance |
16 | \brief A generic platform Vulkan instance implementation. |
17 | \since 5.10 |
18 | \internal |
19 | \ingroup qpa |
20 | |
21 | Implements QPlatformVulkanInstance, serving as a base for platform-specific |
22 | implementations. The library loading and any WSI-specifics are excluded. |
23 | |
24 | Subclasses are expected to call init() from their constructor and |
25 | initInstance() from their createOrAdoptInstance() implementation. |
26 | */ |
27 | |
28 | QBasicPlatformVulkanInstance::QBasicPlatformVulkanInstance() |
29 | { |
30 | } |
31 | |
32 | QBasicPlatformVulkanInstance::~QBasicPlatformVulkanInstance() |
33 | { |
34 | if (!m_vkInst) |
35 | return; |
36 | |
37 | #ifdef VK_EXT_debug_utils |
38 | if (m_debugMessenger) |
39 | m_vkDestroyDebugUtilsMessengerEXT(m_vkInst, m_debugMessenger, nullptr); |
40 | #endif |
41 | |
42 | if (m_ownsVkInst) |
43 | m_vkDestroyInstance(m_vkInst, nullptr); |
44 | } |
45 | |
46 | void QBasicPlatformVulkanInstance::loadVulkanLibrary(const QString &defaultLibraryName, int defaultLibraryVersion) |
47 | { |
48 | QVarLengthArray<std::pair<QString, int>, 3> loadList; |
49 | |
50 | // First in the list of libraries to try is the manual override, relevant on |
51 | // embedded systems without a Vulkan loader and possibly with custom vendor |
52 | // library names. |
53 | if (qEnvironmentVariableIsSet(varName: "QT_VULKAN_LIB" )) |
54 | loadList.append(t: { QString::fromUtf8(ba: qgetenv(varName: "QT_VULKAN_LIB" )), -1 }); |
55 | |
56 | // Then what the platform specified. On Linux the version is likely 1, thus |
57 | // preferring libvulkan.so.1 over libvulkan.so. |
58 | loadList.append(t: { defaultLibraryName, defaultLibraryVersion }); |
59 | |
60 | // If there was a version given, we must still try without it if the first |
61 | // attempt fails, so that libvulkan.so is picked up if the .so.1 is not |
62 | // present on the system (so loaderless embedded systems still work). |
63 | if (defaultLibraryVersion >= 0) |
64 | loadList.append(t: { defaultLibraryName, -1 }); |
65 | |
66 | bool ok = false; |
67 | for (const auto &lib : loadList) { |
68 | m_vulkanLib.reset(p: new QLibrary); |
69 | if (lib.second >= 0) |
70 | m_vulkanLib->setFileNameAndVersion(fileName: lib.first, verNum: lib.second); |
71 | else |
72 | m_vulkanLib->setFileName(lib.first); |
73 | if (m_vulkanLib->load()) { |
74 | ok = true; |
75 | break; |
76 | } |
77 | } |
78 | |
79 | if (!ok) { |
80 | qWarning(msg: "Failed to load %s: %s" , qPrintable(m_vulkanLib->fileName()), qPrintable(m_vulkanLib->errorString())); |
81 | return; |
82 | } |
83 | |
84 | init(lib: m_vulkanLib.get()); |
85 | } |
86 | |
87 | void QBasicPlatformVulkanInstance::init(QLibrary *lib) |
88 | { |
89 | if (m_vkGetInstanceProcAddr) |
90 | return; |
91 | |
92 | qCDebug(lcPlatVk, "Vulkan init (%s)" , qPrintable(lib->fileName())); |
93 | |
94 | // While not strictly required with every implementation, try to follow the spec |
95 | // and do not rely on core functions being exported. |
96 | // |
97 | // 1. dlsym vkGetInstanceProcAddr |
98 | // 2. with a special null instance resolve vkCreateInstance and vkEnumerateInstance* |
99 | // 3. all other core functions are resolved with the created instance |
100 | |
101 | m_vkGetInstanceProcAddr = reinterpret_cast<PFN_vkGetInstanceProcAddr>(lib->resolve(symbol: "vkGetInstanceProcAddr" )); |
102 | if (!m_vkGetInstanceProcAddr) { |
103 | qWarning(msg: "Failed to find vkGetInstanceProcAddr" ); |
104 | return; |
105 | } |
106 | |
107 | m_vkCreateInstance = reinterpret_cast<PFN_vkCreateInstance>(m_vkGetInstanceProcAddr(VK_NULL_HANDLE, "vkCreateInstance" )); |
108 | if (!m_vkCreateInstance) { |
109 | qWarning(msg: "Failed to find vkCreateInstance" ); |
110 | return; |
111 | } |
112 | m_vkEnumerateInstanceLayerProperties = reinterpret_cast<PFN_vkEnumerateInstanceLayerProperties>( |
113 | m_vkGetInstanceProcAddr(VK_NULL_HANDLE, "vkEnumerateInstanceLayerProperties" )); |
114 | if (!m_vkEnumerateInstanceLayerProperties) { |
115 | qWarning(msg: "Failed to find vkEnumerateInstanceLayerProperties" ); |
116 | return; |
117 | } |
118 | m_vkEnumerateInstanceExtensionProperties = reinterpret_cast<PFN_vkEnumerateInstanceExtensionProperties>( |
119 | m_vkGetInstanceProcAddr(VK_NULL_HANDLE, "vkEnumerateInstanceExtensionProperties" )); |
120 | if (!m_vkEnumerateInstanceExtensionProperties) { |
121 | qWarning(msg: "Failed to find vkEnumerateInstanceExtensionProperties" ); |
122 | return; |
123 | } |
124 | |
125 | // Do not rely on non-1.0 header typedefs here. |
126 | typedef VkResult (VKAPI_PTR *T_enumerateInstanceVersion)(uint32_t* pApiVersion); |
127 | // Determine instance-level version as described in the Vulkan 1.2 spec. |
128 | T_enumerateInstanceVersion enumerateInstanceVersion = reinterpret_cast<T_enumerateInstanceVersion>( |
129 | m_vkGetInstanceProcAddr(VK_NULL_HANDLE, "vkEnumerateInstanceVersion" )); |
130 | if (enumerateInstanceVersion) { |
131 | uint32_t ver = 0; |
132 | if (enumerateInstanceVersion(&ver) == VK_SUCCESS) { |
133 | m_supportedApiVersion = QVersionNumber(VK_VERSION_MAJOR(ver), |
134 | VK_VERSION_MINOR(ver), |
135 | VK_VERSION_PATCH(ver)); |
136 | } else { |
137 | m_supportedApiVersion = QVersionNumber(1, 0, 0); |
138 | } |
139 | } else { |
140 | // Vulkan 1.0 |
141 | m_supportedApiVersion = QVersionNumber(1, 0, 0); |
142 | } |
143 | |
144 | uint32_t layerCount = 0; |
145 | m_vkEnumerateInstanceLayerProperties(&layerCount, nullptr); |
146 | if (layerCount) { |
147 | QList<VkLayerProperties> layerProps(layerCount); |
148 | m_vkEnumerateInstanceLayerProperties(&layerCount, layerProps.data()); |
149 | m_supportedLayers.reserve(asize: layerCount); |
150 | for (const VkLayerProperties &p : std::as_const(t&: layerProps)) { |
151 | QVulkanLayer layer; |
152 | layer.name = p.layerName; |
153 | layer.version = p.implementationVersion; |
154 | layer.specVersion = QVersionNumber(VK_VERSION_MAJOR(p.specVersion), |
155 | VK_VERSION_MINOR(p.specVersion), |
156 | VK_VERSION_PATCH(p.specVersion)); |
157 | layer.description = p.description; |
158 | m_supportedLayers.append(t: layer); |
159 | } |
160 | } |
161 | qCDebug(lcPlatVk) << "Supported Vulkan instance layers:" << m_supportedLayers; |
162 | |
163 | uint32_t extCount = 0; |
164 | m_vkEnumerateInstanceExtensionProperties(nullptr, &extCount, nullptr); |
165 | if (extCount) { |
166 | QList<VkExtensionProperties> extProps(extCount); |
167 | m_vkEnumerateInstanceExtensionProperties(nullptr, &extCount, extProps.data()); |
168 | m_supportedExtensions.reserve(asize: extCount); |
169 | for (const VkExtensionProperties &p : std::as_const(t&: extProps)) { |
170 | QVulkanExtension ext; |
171 | ext.name = p.extensionName; |
172 | ext.version = p.specVersion; |
173 | m_supportedExtensions.append(t: ext); |
174 | } |
175 | } |
176 | qDebug(catFunc: lcPlatVk) << "Supported Vulkan instance extensions:" << m_supportedExtensions; |
177 | } |
178 | |
179 | QVulkanInfoVector<QVulkanLayer> QBasicPlatformVulkanInstance::supportedLayers() const |
180 | { |
181 | return m_supportedLayers; |
182 | } |
183 | |
184 | QVulkanInfoVector<QVulkanExtension> QBasicPlatformVulkanInstance::supportedExtensions() const |
185 | { |
186 | return m_supportedExtensions; |
187 | } |
188 | |
189 | QVersionNumber QBasicPlatformVulkanInstance::supportedApiVersion() const |
190 | { |
191 | return m_supportedApiVersion; |
192 | } |
193 | |
194 | void QBasicPlatformVulkanInstance::initInstance(QVulkanInstance *instance, const QByteArrayList &) |
195 | { |
196 | if (!m_vkGetInstanceProcAddr) { |
197 | qWarning(msg: "initInstance: No Vulkan library available" ); |
198 | return; |
199 | } |
200 | |
201 | m_vkInst = instance->vkInstance(); // when non-null we are adopting an existing instance |
202 | |
203 | QVulkanInstance::Flags flags = instance->flags(); |
204 | m_enabledLayers = instance->layers(); |
205 | m_enabledExtensions = instance->extensions(); |
206 | |
207 | if (!m_vkInst) { |
208 | VkApplicationInfo appInfo = {}; |
209 | appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; |
210 | QByteArray appName = QCoreApplication::applicationName().toUtf8(); |
211 | appInfo.pApplicationName = appName.constData(); |
212 | const QVersionNumber apiVersion = instance->apiVersion(); |
213 | if (!apiVersion.isNull()) { |
214 | appInfo.apiVersion = VK_MAKE_VERSION(apiVersion.majorVersion(), |
215 | apiVersion.minorVersion(), |
216 | apiVersion.microVersion()); |
217 | } |
218 | |
219 | m_enabledExtensions.append(t: "VK_KHR_surface" ); |
220 | if (!flags.testFlag(flag: QVulkanInstance::NoPortabilityDrivers)) |
221 | m_enabledExtensions.append(t: "VK_KHR_portability_enumeration" ); |
222 | if (!flags.testFlag(flag: QVulkanInstance::NoDebugOutputRedirect)) |
223 | m_enabledExtensions.append(t: "VK_EXT_debug_utils" ); |
224 | |
225 | for (const QByteArray &ext : extraExts) |
226 | m_enabledExtensions.append(t: ext); |
227 | |
228 | QByteArray envExts = qgetenv(varName: "QT_VULKAN_INSTANCE_EXTENSIONS" ); |
229 | if (!envExts.isEmpty()) { |
230 | QByteArrayList envExtList = envExts.split(sep: ';'); |
231 | for (auto ext : m_enabledExtensions) |
232 | envExtList.removeAll(t: ext); |
233 | m_enabledExtensions.append(l: envExtList); |
234 | } |
235 | |
236 | QByteArray envLayers = qgetenv(varName: "QT_VULKAN_INSTANCE_LAYERS" ); |
237 | if (!envLayers.isEmpty()) { |
238 | QByteArrayList envLayerList = envLayers.split(sep: ';'); |
239 | for (auto ext : m_enabledLayers) |
240 | envLayerList.removeAll(t: ext); |
241 | m_enabledLayers.append(l: envLayerList); |
242 | } |
243 | |
244 | // No clever stuff with QSet and friends: the order for layers matters |
245 | // and the user-provided order must be kept. |
246 | for (int i = 0; i < m_enabledLayers.size(); ++i) { |
247 | const QByteArray &layerName(m_enabledLayers[i]); |
248 | if (!m_supportedLayers.contains(name: layerName)) |
249 | m_enabledLayers.removeAt(i: i--); |
250 | } |
251 | qDebug(catFunc: lcPlatVk) << "Enabling Vulkan instance layers:" << m_enabledLayers; |
252 | for (int i = 0; i < m_enabledExtensions.size(); ++i) { |
253 | const QByteArray &extName(m_enabledExtensions[i]); |
254 | if (!m_supportedExtensions.contains(name: extName)) |
255 | m_enabledExtensions.removeAt(i: i--); |
256 | } |
257 | qDebug(catFunc: lcPlatVk) << "Enabling Vulkan instance extensions:" << m_enabledExtensions; |
258 | |
259 | VkInstanceCreateInfo instInfo = {}; |
260 | instInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; |
261 | instInfo.pApplicationInfo = &appInfo; |
262 | if (!flags.testFlag(flag: QVulkanInstance::NoPortabilityDrivers)) |
263 | instInfo.flags |= 0x00000001; // VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR |
264 | |
265 | QList<const char *> layerNameVec; |
266 | for (const QByteArray &ba : std::as_const(t&: m_enabledLayers)) |
267 | layerNameVec.append(t: ba.constData()); |
268 | if (!layerNameVec.isEmpty()) { |
269 | instInfo.enabledLayerCount = layerNameVec.size(); |
270 | instInfo.ppEnabledLayerNames = layerNameVec.constData(); |
271 | } |
272 | |
273 | QList<const char *> extNameVec; |
274 | for (const QByteArray &ba : std::as_const(t&: m_enabledExtensions)) |
275 | extNameVec.append(t: ba.constData()); |
276 | if (!extNameVec.isEmpty()) { |
277 | instInfo.enabledExtensionCount = extNameVec.size(); |
278 | instInfo.ppEnabledExtensionNames = extNameVec.constData(); |
279 | } |
280 | |
281 | m_errorCode = m_vkCreateInstance(&instInfo, nullptr, &m_vkInst); |
282 | if (m_errorCode != VK_SUCCESS || !m_vkInst) { |
283 | qWarning(msg: "Failed to create Vulkan instance: %d" , m_errorCode); |
284 | return; |
285 | } |
286 | |
287 | m_vkDestroyInstance = reinterpret_cast<PFN_vkDestroyInstance>(m_vkGetInstanceProcAddr(m_vkInst, "vkDestroyInstance" )); |
288 | if (!m_vkDestroyInstance) { |
289 | qWarning(msg: "Failed to find vkDestroyInstance" ); |
290 | m_vkInst = VK_NULL_HANDLE; |
291 | return; |
292 | } |
293 | |
294 | m_ownsVkInst = true; |
295 | } |
296 | |
297 | m_getPhysDevSurfaceSupport = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfaceSupportKHR>( |
298 | m_vkGetInstanceProcAddr(m_vkInst, "vkGetPhysicalDeviceSurfaceSupportKHR" )); |
299 | if (!m_getPhysDevSurfaceSupport) |
300 | qWarning(msg: "Failed to find vkGetPhysicalDeviceSurfaceSupportKHR" ); |
301 | |
302 | m_destroySurface = reinterpret_cast<PFN_vkDestroySurfaceKHR>( |
303 | m_vkGetInstanceProcAddr(m_vkInst, "vkDestroySurfaceKHR" )); |
304 | if (!m_destroySurface) |
305 | qWarning(msg: "Failed to find vkDestroySurfaceKHR" ); |
306 | |
307 | if (!flags.testFlag(flag: QVulkanInstance::NoDebugOutputRedirect)) |
308 | setupDebugOutput(); |
309 | } |
310 | |
311 | bool QBasicPlatformVulkanInstance::isValid() const |
312 | { |
313 | return m_vkInst != VK_NULL_HANDLE; |
314 | } |
315 | |
316 | VkResult QBasicPlatformVulkanInstance::errorCode() const |
317 | { |
318 | return m_errorCode; |
319 | } |
320 | |
321 | VkInstance QBasicPlatformVulkanInstance::vkInstance() const |
322 | { |
323 | return m_vkInst; |
324 | } |
325 | |
326 | QByteArrayList QBasicPlatformVulkanInstance::enabledLayers() const |
327 | { |
328 | return m_enabledLayers; |
329 | } |
330 | |
331 | QByteArrayList QBasicPlatformVulkanInstance::enabledExtensions() const |
332 | { |
333 | return m_enabledExtensions; |
334 | } |
335 | |
336 | PFN_vkVoidFunction QBasicPlatformVulkanInstance::getInstanceProcAddr(const char *name) |
337 | { |
338 | if (!name) |
339 | return nullptr; |
340 | |
341 | const bool needsNullInstance = !strcmp(s1: name, s2: "vkEnumerateInstanceLayerProperties" ) |
342 | || !strcmp(s1: name, s2: "vkEnumerateInstanceExtensionProperties" ); |
343 | |
344 | return m_vkGetInstanceProcAddr(needsNullInstance ? 0 : m_vkInst, name); |
345 | } |
346 | |
347 | bool QBasicPlatformVulkanInstance::supportsPresent(VkPhysicalDevice physicalDevice, |
348 | uint32_t queueFamilyIndex, |
349 | QWindow *window) |
350 | { |
351 | if (!m_getPhysDevSurfaceSupport) |
352 | return true; |
353 | |
354 | VkSurfaceKHR surface = QVulkanInstance::surfaceForWindow(window); |
355 | VkBool32 supported = false; |
356 | m_getPhysDevSurfaceSupport(physicalDevice, queueFamilyIndex, surface, &supported); |
357 | |
358 | return supported; |
359 | } |
360 | |
361 | void QBasicPlatformVulkanInstance::setDebugFilters(const QList<QVulkanInstance::DebugFilter> &filters) |
362 | { |
363 | m_debugFilters = filters; |
364 | } |
365 | |
366 | void QBasicPlatformVulkanInstance::setDebugUtilsFilters(const QList<QVulkanInstance::DebugUtilsFilter> &filters) |
367 | { |
368 | m_debugUtilsFilters = filters; |
369 | } |
370 | |
371 | void QBasicPlatformVulkanInstance::destroySurface(VkSurfaceKHR surface) const |
372 | { |
373 | if (m_destroySurface && surface) |
374 | m_destroySurface(m_vkInst, surface, nullptr); |
375 | } |
376 | |
377 | #ifdef VK_EXT_debug_utils |
378 | static VKAPI_ATTR VkBool32 VKAPI_CALL defaultDebugCallbackFunc(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, |
379 | VkDebugUtilsMessageTypeFlagsEXT messageType, |
380 | const VkDebugUtilsMessengerCallbackDataEXT *pCallbackData, |
381 | void *pUserData) |
382 | { |
383 | QBasicPlatformVulkanInstance *self = static_cast<QBasicPlatformVulkanInstance *>(pUserData); |
384 | |
385 | // legacy filters |
386 | for (QVulkanInstance::DebugFilter filter : *self->debugFilters()) { |
387 | // As per docs in qvulkaninstance.cpp we pass object, messageCode, |
388 | // pMessage to the callback with the legacy signature. |
389 | uint64_t object = 0; |
390 | if (pCallbackData->objectCount > 0) |
391 | object = pCallbackData->pObjects[0].objectHandle; |
392 | if (filter(0, VK_DEBUG_REPORT_OBJECT_TYPE_UNKNOWN_EXT, object, 0, |
393 | pCallbackData->messageIdNumber, "" , pCallbackData->pMessage)) |
394 | { |
395 | return VK_FALSE; |
396 | } |
397 | } |
398 | |
399 | // filters with new signature |
400 | for (QVulkanInstance::DebugUtilsFilter filter : *self->debugUtilsFilters()) { |
401 | QVulkanInstance::DebugMessageSeverityFlags severity; |
402 | if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT) |
403 | severity |= QVulkanInstance::VerboseSeverity; |
404 | if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) |
405 | severity |= QVulkanInstance::InfoSeverity; |
406 | if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) |
407 | severity |= QVulkanInstance::WarningSeverity; |
408 | if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) |
409 | severity |= QVulkanInstance::ErrorSeverity; |
410 | QVulkanInstance::DebugMessageTypeFlags type; |
411 | if (messageType & VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT) |
412 | type |= QVulkanInstance::GeneralMessage; |
413 | if (messageType & VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT) |
414 | type |= QVulkanInstance::ValidationMessage; |
415 | if (messageType & VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT) |
416 | type |= QVulkanInstance::PerformanceMessage; |
417 | if (filter(severity, type, pCallbackData)) |
418 | return VK_FALSE; |
419 | } |
420 | |
421 | // not categorized, just route to plain old qDebug |
422 | qDebug(msg: "vkDebug: %s" , pCallbackData->pMessage); |
423 | |
424 | return VK_FALSE; |
425 | } |
426 | #endif |
427 | |
428 | void QBasicPlatformVulkanInstance::setupDebugOutput() |
429 | { |
430 | #ifdef VK_EXT_debug_utils |
431 | if (!m_enabledExtensions.contains(t: "VK_EXT_debug_utils" )) |
432 | return; |
433 | |
434 | PFN_vkCreateDebugUtilsMessengerEXT vkCreateDebugUtilsMessengerEXT = reinterpret_cast<PFN_vkCreateDebugUtilsMessengerEXT>( |
435 | m_vkGetInstanceProcAddr(m_vkInst, "vkCreateDebugUtilsMessengerEXT" )); |
436 | |
437 | m_vkDestroyDebugUtilsMessengerEXT = reinterpret_cast<PFN_vkDestroyDebugUtilsMessengerEXT>( |
438 | m_vkGetInstanceProcAddr(m_vkInst, "vkDestroyDebugUtilsMessengerEXT" )); |
439 | |
440 | VkDebugUtilsMessengerCreateInfoEXT messengerInfo = {}; |
441 | messengerInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; |
442 | messengerInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
443 | | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; |
444 | messengerInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
445 | | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
446 | | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; |
447 | messengerInfo.pfnUserCallback = defaultDebugCallbackFunc; |
448 | messengerInfo.pUserData = this; |
449 | VkResult err = vkCreateDebugUtilsMessengerEXT(m_vkInst, &messengerInfo, nullptr, &m_debugMessenger); |
450 | if (err != VK_SUCCESS) |
451 | qWarning(msg: "Failed to create debug report callback: %d" , err); |
452 | #endif |
453 | } |
454 | |
455 | QT_END_NAMESPACE |
456 | |