1 /****************************************************************************
2 **
3 ** Copyright (C) 2017 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the plugins of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include "qbasicvulkanplatforminstance_p.h"
41 #include <QLibrary>
42 #include <QCoreApplication>
43 #include <QVector>
44 #include <QLoggingCategory>
45 
46 QT_BEGIN_NAMESPACE
47 
48 Q_LOGGING_CATEGORY(lcPlatVk, "qt.vulkan")
49 
50 /*!
51     \class QBasicPlatformVulkanInstance
52     \brief A generic platform Vulkan instance implementation.
53     \since 5.10
54     \internal
55     \ingroup qpa
56 
57     Implements QPlatformVulkanInstance, serving as a base for platform-specific
58     implementations. The library loading and any WSI-specifics are excluded.
59 
60     Subclasses are expected to call init() from their constructor and
61     initInstance() from their createOrAdoptInstance() implementation.
62  */
63 
QBasicPlatformVulkanInstance()64 QBasicPlatformVulkanInstance::QBasicPlatformVulkanInstance()
65     : m_vkInst(VK_NULL_HANDLE),
66       m_vkGetInstanceProcAddr(nullptr),
67       m_ownsVkInst(false),
68       m_errorCode(VK_SUCCESS),
69       m_debugCallback(0)
70 {
71 }
72 
~QBasicPlatformVulkanInstance()73 QBasicPlatformVulkanInstance::~QBasicPlatformVulkanInstance()
74 {
75     if (!m_vkInst)
76         return;
77 
78     if (m_debugCallback && m_vkDestroyDebugReportCallbackEXT)
79         m_vkDestroyDebugReportCallbackEXT(m_vkInst, m_debugCallback, nullptr);
80 
81     if (m_ownsVkInst)
82         m_vkDestroyInstance(m_vkInst, nullptr);
83 }
84 
loadVulkanLibrary(const QString & defaultLibraryName)85 void QBasicPlatformVulkanInstance::loadVulkanLibrary(const QString &defaultLibraryName)
86 {
87     if (qEnvironmentVariableIsSet("QT_VULKAN_LIB"))
88         m_vulkanLib.setFileName(QString::fromUtf8(qgetenv("QT_VULKAN_LIB")));
89     else
90         m_vulkanLib.setFileName(defaultLibraryName);
91 
92     if (!m_vulkanLib.load()) {
93         qWarning("Failed to load %s: %s", qPrintable(m_vulkanLib.fileName()), qPrintable(m_vulkanLib.errorString()));
94         return;
95     }
96 
97     init(&m_vulkanLib);
98 }
99 
init(QLibrary * lib)100 void QBasicPlatformVulkanInstance::init(QLibrary *lib)
101 {
102     if (m_vkGetInstanceProcAddr)
103         return;
104 
105     qCDebug(lcPlatVk, "Vulkan init (%s)", qPrintable(lib->fileName()));
106 
107     // While not strictly required with every implementation, try to follow the spec
108     // and do not rely on core functions being exported.
109     //
110     // 1. dlsym vkGetInstanceProcAddr
111     // 2. with a special null instance resolve vkCreateInstance and vkEnumerateInstance*
112     // 3. all other core functions are resolved with the created instance
113 
114     m_vkGetInstanceProcAddr = reinterpret_cast<PFN_vkGetInstanceProcAddr>(lib->resolve("vkGetInstanceProcAddr"));
115     if (!m_vkGetInstanceProcAddr) {
116         qWarning("Failed to find vkGetInstanceProcAddr");
117         return;
118     }
119 
120     m_vkCreateInstance = reinterpret_cast<PFN_vkCreateInstance>(m_vkGetInstanceProcAddr(VK_NULL_HANDLE, "vkCreateInstance"));
121     if (!m_vkCreateInstance) {
122         qWarning("Failed to find vkCreateInstance");
123         return;
124     }
125     m_vkEnumerateInstanceLayerProperties = reinterpret_cast<PFN_vkEnumerateInstanceLayerProperties>(
126                 m_vkGetInstanceProcAddr(VK_NULL_HANDLE, "vkEnumerateInstanceLayerProperties"));
127     if (!m_vkEnumerateInstanceLayerProperties) {
128         qWarning("Failed to find vkEnumerateInstanceLayerProperties");
129         return;
130     }
131     m_vkEnumerateInstanceExtensionProperties = reinterpret_cast<PFN_vkEnumerateInstanceExtensionProperties>(
132                 m_vkGetInstanceProcAddr(VK_NULL_HANDLE, "vkEnumerateInstanceExtensionProperties"));
133     if (!m_vkEnumerateInstanceExtensionProperties) {
134         qWarning("Failed to find vkEnumerateInstanceExtensionProperties");
135         return;
136     }
137 
138     uint32_t layerCount = 0;
139     m_vkEnumerateInstanceLayerProperties(&layerCount, nullptr);
140     if (layerCount) {
141         QVector<VkLayerProperties> layerProps(layerCount);
142         m_vkEnumerateInstanceLayerProperties(&layerCount, layerProps.data());
143         m_supportedLayers.reserve(layerCount);
144         for (const VkLayerProperties &p : qAsConst(layerProps)) {
145             QVulkanLayer layer;
146             layer.name = p.layerName;
147             layer.version = p.implementationVersion;
148             layer.specVersion = QVersionNumber(VK_VERSION_MAJOR(p.specVersion),
149                                                VK_VERSION_MINOR(p.specVersion),
150                                                VK_VERSION_PATCH(p.specVersion));
151             layer.description = p.description;
152             m_supportedLayers.append(layer);
153         }
154     }
155     qCDebug(lcPlatVk) << "Supported Vulkan instance layers:" << m_supportedLayers;
156 
157     uint32_t extCount = 0;
158     m_vkEnumerateInstanceExtensionProperties(nullptr, &extCount, nullptr);
159     if (extCount) {
160         QVector<VkExtensionProperties> extProps(extCount);
161         m_vkEnumerateInstanceExtensionProperties(nullptr, &extCount, extProps.data());
162         m_supportedExtensions.reserve(extCount);
163         for (const VkExtensionProperties &p : qAsConst(extProps)) {
164             QVulkanExtension ext;
165             ext.name = p.extensionName;
166             ext.version = p.specVersion;
167             m_supportedExtensions.append(ext);
168         }
169     }
170     qDebug(lcPlatVk) << "Supported Vulkan instance extensions:" << m_supportedExtensions;
171 }
172 
supportedLayers() const173 QVulkanInfoVector<QVulkanLayer> QBasicPlatformVulkanInstance::supportedLayers() const
174 {
175     return m_supportedLayers;
176 }
177 
supportedExtensions() const178 QVulkanInfoVector<QVulkanExtension> QBasicPlatformVulkanInstance::supportedExtensions() const
179 {
180     return m_supportedExtensions;
181 }
182 
initInstance(QVulkanInstance * instance,const QByteArrayList & extraExts)183 void QBasicPlatformVulkanInstance::initInstance(QVulkanInstance *instance, const QByteArrayList &extraExts)
184 {
185     if (!m_vkGetInstanceProcAddr) {
186         qWarning("initInstance: No Vulkan library available");
187         return;
188     }
189 
190     m_vkInst = instance->vkInstance(); // when non-null we are adopting an existing instance
191 
192     QVulkanInstance::Flags flags = instance->flags();
193     m_enabledLayers = instance->layers();
194     m_enabledExtensions = instance->extensions();
195 
196     if (!m_vkInst) {
197         VkApplicationInfo appInfo;
198         memset(&appInfo, 0, sizeof(appInfo));
199         appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
200         QByteArray appName = QCoreApplication::applicationName().toUtf8();
201         appInfo.pApplicationName = appName.constData();
202         const QVersionNumber apiVersion = instance->apiVersion();
203         if (!apiVersion.isNull()) {
204             appInfo.apiVersion = VK_MAKE_VERSION(apiVersion.majorVersion(),
205                                                  apiVersion.minorVersion(),
206                                                  apiVersion.microVersion());
207         }
208 
209         if (!flags.testFlag(QVulkanInstance::NoDebugOutputRedirect))
210             m_enabledExtensions.append("VK_EXT_debug_report");
211 
212         m_enabledExtensions.append("VK_KHR_surface");
213 
214         for (const QByteArray &ext : extraExts)
215             m_enabledExtensions.append(ext);
216 
217         QByteArray envExts = qgetenv("QT_VULKAN_INSTANCE_EXTENSIONS");
218         if (!envExts.isEmpty()) {
219             QByteArrayList envExtList =  envExts.split(';');
220             for (auto ext : m_enabledExtensions)
221                 envExtList.removeAll(ext);
222             m_enabledExtensions.append(envExtList);
223         }
224 
225         QByteArray envLayers = qgetenv("QT_VULKAN_INSTANCE_LAYERS");
226         if (!envLayers.isEmpty()) {
227             QByteArrayList envLayerList = envLayers.split(';');
228             for (auto ext : m_enabledLayers)
229                 envLayerList.removeAll(ext);
230             m_enabledLayers.append(envLayerList);
231         }
232 
233         // No clever stuff with QSet and friends: the order for layers matters
234         // and the user-provided order must be kept.
235         for (int i = 0; i < m_enabledLayers.count(); ++i) {
236             const QByteArray &layerName(m_enabledLayers[i]);
237             if (!m_supportedLayers.contains(layerName))
238                 m_enabledLayers.removeAt(i--);
239         }
240         qDebug(lcPlatVk) << "Enabling Vulkan instance layers:" << m_enabledLayers;
241         for (int i = 0; i < m_enabledExtensions.count(); ++i) {
242             const QByteArray &extName(m_enabledExtensions[i]);
243             if (!m_supportedExtensions.contains(extName))
244                 m_enabledExtensions.removeAt(i--);
245         }
246         qDebug(lcPlatVk) << "Enabling Vulkan instance extensions:" << m_enabledExtensions;
247 
248         VkInstanceCreateInfo instInfo;
249         memset(&instInfo, 0, sizeof(instInfo));
250         instInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
251         instInfo.pApplicationInfo = &appInfo;
252 
253         QVector<const char *> layerNameVec;
254         for (const QByteArray &ba : qAsConst(m_enabledLayers))
255             layerNameVec.append(ba.constData());
256         if (!layerNameVec.isEmpty()) {
257             instInfo.enabledLayerCount = layerNameVec.count();
258             instInfo.ppEnabledLayerNames = layerNameVec.constData();
259         }
260 
261         QVector<const char *> extNameVec;
262         for (const QByteArray &ba : qAsConst(m_enabledExtensions))
263             extNameVec.append(ba.constData());
264         if (!extNameVec.isEmpty()) {
265             instInfo.enabledExtensionCount = extNameVec.count();
266             instInfo.ppEnabledExtensionNames = extNameVec.constData();
267         }
268 
269         m_errorCode = m_vkCreateInstance(&instInfo, nullptr, &m_vkInst);
270         if (m_errorCode != VK_SUCCESS || !m_vkInst) {
271             qWarning("Failed to create Vulkan instance: %d", m_errorCode);
272             return;
273         }
274 
275         m_vkDestroyInstance = reinterpret_cast<PFN_vkDestroyInstance>(m_vkGetInstanceProcAddr(m_vkInst, "vkDestroyInstance"));
276         if (!m_vkDestroyInstance) {
277             qWarning("Failed to find vkDestroyInstance");
278             m_vkInst = VK_NULL_HANDLE;
279             return;
280         }
281 
282         m_ownsVkInst = true;
283     }
284 
285     m_getPhysDevSurfaceSupport = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfaceSupportKHR>(
286                 m_vkGetInstanceProcAddr(m_vkInst, "vkGetPhysicalDeviceSurfaceSupportKHR"));
287     if (!m_getPhysDevSurfaceSupport)
288         qWarning("Failed to find vkGetPhysicalDeviceSurfaceSupportKHR");
289 
290     m_destroySurface = reinterpret_cast<PFN_vkDestroySurfaceKHR>(
291                 m_vkGetInstanceProcAddr(m_vkInst, "vkDestroySurfaceKHR"));
292     if (!m_destroySurface)
293         qWarning("Failed to find vkDestroySurfaceKHR");
294 
295     if (!flags.testFlag(QVulkanInstance::NoDebugOutputRedirect))
296         setupDebugOutput();
297 }
298 
isValid() const299 bool QBasicPlatformVulkanInstance::isValid() const
300 {
301     return m_vkInst != VK_NULL_HANDLE;
302 }
303 
errorCode() const304 VkResult QBasicPlatformVulkanInstance::errorCode() const
305 {
306     return m_errorCode;
307 }
308 
vkInstance() const309 VkInstance QBasicPlatformVulkanInstance::vkInstance() const
310 {
311     return m_vkInst;
312 }
313 
enabledLayers() const314 QByteArrayList QBasicPlatformVulkanInstance::enabledLayers() const
315 {
316     return m_enabledLayers;
317 }
318 
enabledExtensions() const319 QByteArrayList QBasicPlatformVulkanInstance::enabledExtensions() const
320 {
321     return m_enabledExtensions;
322 }
323 
getInstanceProcAddr(const char * name)324 PFN_vkVoidFunction QBasicPlatformVulkanInstance::getInstanceProcAddr(const char *name)
325 {
326     if (!name)
327         return nullptr;
328 
329     const bool needsNullInstance = !strcmp(name, "vkEnumerateInstanceLayerProperties")
330             || !strcmp(name, "vkEnumerateInstanceExtensionProperties");
331 
332     return m_vkGetInstanceProcAddr(needsNullInstance ? 0 : m_vkInst, name);
333 }
334 
supportsPresent(VkPhysicalDevice physicalDevice,uint32_t queueFamilyIndex,QWindow * window)335 bool QBasicPlatformVulkanInstance::supportsPresent(VkPhysicalDevice physicalDevice,
336                                                    uint32_t queueFamilyIndex,
337                                                    QWindow *window)
338 {
339     if (!m_getPhysDevSurfaceSupport)
340         return true;
341 
342     VkSurfaceKHR surface = QVulkanInstance::surfaceForWindow(window);
343     VkBool32 supported = false;
344     m_getPhysDevSurfaceSupport(physicalDevice, queueFamilyIndex, surface, &supported);
345 
346     return supported;
347 }
348 
setDebugFilters(const QVector<QVulkanInstance::DebugFilter> & filters)349 void QBasicPlatformVulkanInstance::setDebugFilters(const QVector<QVulkanInstance::DebugFilter> &filters)
350 {
351     m_debugFilters = filters;
352 }
353 
destroySurface(VkSurfaceKHR surface) const354 void QBasicPlatformVulkanInstance::destroySurface(VkSurfaceKHR surface) const
355 {
356     if (m_destroySurface && surface)
357         m_destroySurface(m_vkInst, surface, nullptr);
358 }
359 
defaultDebugCallbackFunc(VkDebugReportFlagsEXT flags,VkDebugReportObjectTypeEXT objectType,uint64_t object,size_t location,int32_t messageCode,const char * pLayerPrefix,const char * pMessage,void * pUserData)360 static VKAPI_ATTR VkBool32 VKAPI_CALL defaultDebugCallbackFunc(VkDebugReportFlagsEXT flags,
361                                                                VkDebugReportObjectTypeEXT objectType,
362                                                                uint64_t object,
363                                                                size_t location,
364                                                                int32_t messageCode,
365                                                                const char *pLayerPrefix,
366                                                                const char *pMessage,
367                                                                void *pUserData)
368 {
369     QBasicPlatformVulkanInstance *self = static_cast<QBasicPlatformVulkanInstance *>(pUserData);
370     for (QVulkanInstance::DebugFilter filter : *self->debugFilters()) {
371         if (filter(flags, objectType, object, location, messageCode, pLayerPrefix, pMessage))
372             return VK_FALSE;
373     }
374 
375     // not categorized, just route to plain old qDebug
376     qDebug("vkDebug: %s: %d: %s", pLayerPrefix, messageCode, pMessage);
377 
378     return VK_FALSE;
379 }
380 
setupDebugOutput()381 void QBasicPlatformVulkanInstance::setupDebugOutput()
382 {
383     if (!m_enabledExtensions.contains("VK_EXT_debug_report"))
384         return;
385 
386     PFN_vkCreateDebugReportCallbackEXT createDebugReportCallback = reinterpret_cast<PFN_vkCreateDebugReportCallbackEXT>(
387                 m_vkGetInstanceProcAddr(m_vkInst, "vkCreateDebugReportCallbackEXT"));
388     m_vkDestroyDebugReportCallbackEXT = reinterpret_cast<PFN_vkDestroyDebugReportCallbackEXT>(
389                 m_vkGetInstanceProcAddr(m_vkInst, "vkDestroyDebugReportCallbackEXT"));
390 
391     VkDebugReportCallbackCreateInfoEXT dbgCallbackInfo;
392     memset(&dbgCallbackInfo, 0, sizeof(dbgCallbackInfo));
393     dbgCallbackInfo.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CREATE_INFO_EXT;
394     dbgCallbackInfo.flags =  VK_DEBUG_REPORT_ERROR_BIT_EXT
395             | VK_DEBUG_REPORT_WARNING_BIT_EXT
396             | VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT;
397     dbgCallbackInfo.pfnCallback = defaultDebugCallbackFunc;
398     dbgCallbackInfo.pUserData = this;
399 
400     VkResult err = createDebugReportCallback(m_vkInst, &dbgCallbackInfo, nullptr, &m_debugCallback);
401     if (err != VK_SUCCESS)
402         qWarning("Failed to create debug report callback: %d", err);
403 }
404 
405 QT_END_NAMESPACE
406