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
10QT_BEGIN_NAMESPACE
11
12Q_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
28QBasicPlatformVulkanInstance::QBasicPlatformVulkanInstance()
29{
30}
31
32QBasicPlatformVulkanInstance::~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
46void 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
87void 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
179QVulkanInfoVector<QVulkanLayer> QBasicPlatformVulkanInstance::supportedLayers() const
180{
181 return m_supportedLayers;
182}
183
184QVulkanInfoVector<QVulkanExtension> QBasicPlatformVulkanInstance::supportedExtensions() const
185{
186 return m_supportedExtensions;
187}
188
189QVersionNumber QBasicPlatformVulkanInstance::supportedApiVersion() const
190{
191 return m_supportedApiVersion;
192}
193
194void QBasicPlatformVulkanInstance::initInstance(QVulkanInstance *instance, const QByteArrayList &extraExts)
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
311bool QBasicPlatformVulkanInstance::isValid() const
312{
313 return m_vkInst != VK_NULL_HANDLE;
314}
315
316VkResult QBasicPlatformVulkanInstance::errorCode() const
317{
318 return m_errorCode;
319}
320
321VkInstance QBasicPlatformVulkanInstance::vkInstance() const
322{
323 return m_vkInst;
324}
325
326QByteArrayList QBasicPlatformVulkanInstance::enabledLayers() const
327{
328 return m_enabledLayers;
329}
330
331QByteArrayList QBasicPlatformVulkanInstance::enabledExtensions() const
332{
333 return m_enabledExtensions;
334}
335
336PFN_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
347bool 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
361void QBasicPlatformVulkanInstance::setDebugFilters(const QList<QVulkanInstance::DebugFilter> &filters)
362{
363 m_debugFilters = filters;
364}
365
366void QBasicPlatformVulkanInstance::setDebugUtilsFilters(const QList<QVulkanInstance::DebugUtilsFilter> &filters)
367{
368 m_debugUtilsFilters = filters;
369}
370
371void 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
378static 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
428void 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
455QT_END_NAMESPACE
456

source code of qtbase/src/gui/vulkan/qbasicvulkanplatforminstance.cpp