summaryrefslogtreecommitdiffstats
path: root/src/gui/vulkan/qbasicvulkanplatforminstance.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui/vulkan/qbasicvulkanplatforminstance.cpp')
-rw-r--r--src/gui/vulkan/qbasicvulkanplatforminstance.cpp460
1 files changed, 460 insertions, 0 deletions
diff --git a/src/gui/vulkan/qbasicvulkanplatforminstance.cpp b/src/gui/vulkan/qbasicvulkanplatforminstance.cpp
new file mode 100644
index 0000000000..bbe9f9e1cb
--- /dev/null
+++ b/src/gui/vulkan/qbasicvulkanplatforminstance.cpp
@@ -0,0 +1,460 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qbasicvulkanplatforminstance_p.h"
+#include <QCoreApplication>
+#include <QList>
+#include <QLoggingCategory>
+#include <QVarLengthArray>
+
+QT_BEGIN_NAMESPACE
+
+Q_LOGGING_CATEGORY(lcPlatVk, "qt.vulkan")
+
+/*!
+ \class QBasicPlatformVulkanInstance
+ \brief A generic platform Vulkan instance implementation.
+ \since 5.10
+ \internal
+ \ingroup qpa
+
+ Implements QPlatformVulkanInstance, serving as a base for platform-specific
+ implementations. The library loading and any WSI-specifics are excluded.
+
+ Subclasses are expected to call init() from their constructor and
+ initInstance() from their createOrAdoptInstance() implementation.
+ */
+
+QBasicPlatformVulkanInstance::QBasicPlatformVulkanInstance()
+{
+}
+
+QBasicPlatformVulkanInstance::~QBasicPlatformVulkanInstance()
+{
+ if (!m_vkInst)
+ return;
+
+#ifdef VK_EXT_debug_utils
+ if (m_debugMessenger)
+ m_vkDestroyDebugUtilsMessengerEXT(m_vkInst, m_debugMessenger, nullptr);
+#endif
+
+ if (m_ownsVkInst)
+ m_vkDestroyInstance(m_vkInst, nullptr);
+}
+
+void QBasicPlatformVulkanInstance::loadVulkanLibrary(const QString &defaultLibraryName, int defaultLibraryVersion)
+{
+ QVarLengthArray<std::pair<QString, int>, 3> loadList;
+
+ // First in the list of libraries to try is the manual override, relevant on
+ // embedded systems without a Vulkan loader and possibly with custom vendor
+ // library names.
+ if (qEnvironmentVariableIsSet("QT_VULKAN_LIB"))
+ loadList.append({ QString::fromUtf8(qgetenv("QT_VULKAN_LIB")), -1 });
+
+ // Then what the platform specified. On Linux the version is likely 1, thus
+ // preferring libvulkan.so.1 over libvulkan.so.
+ loadList.append({ defaultLibraryName, defaultLibraryVersion });
+
+ // If there was a version given, we must still try without it if the first
+ // attempt fails, so that libvulkan.so is picked up if the .so.1 is not
+ // present on the system (so loaderless embedded systems still work).
+ if (defaultLibraryVersion >= 0)
+ loadList.append({ defaultLibraryName, -1 });
+
+ bool ok = false;
+ for (const auto &lib : loadList) {
+ m_vulkanLib.reset(new QLibrary);
+ if (lib.second >= 0)
+ m_vulkanLib->setFileNameAndVersion(lib.first, lib.second);
+ else
+ m_vulkanLib->setFileName(lib.first);
+ if (m_vulkanLib->load()) {
+ ok = true;
+ break;
+ }
+ }
+
+ if (!ok) {
+ qWarning("Failed to load %s: %s", qPrintable(m_vulkanLib->fileName()), qPrintable(m_vulkanLib->errorString()));
+ return;
+ }
+
+ init(m_vulkanLib.get());
+}
+
+void QBasicPlatformVulkanInstance::init(QLibrary *lib)
+{
+ if (m_vkGetInstanceProcAddr)
+ return;
+
+ qCDebug(lcPlatVk, "Vulkan init (%s)", qPrintable(lib->fileName()));
+
+ // While not strictly required with every implementation, try to follow the spec
+ // and do not rely on core functions being exported.
+ //
+ // 1. dlsym vkGetInstanceProcAddr
+ // 2. with a special null instance resolve vkCreateInstance and vkEnumerateInstance*
+ // 3. all other core functions are resolved with the created instance
+
+ m_vkGetInstanceProcAddr = reinterpret_cast<PFN_vkGetInstanceProcAddr>(lib->resolve("vkGetInstanceProcAddr"));
+ if (!m_vkGetInstanceProcAddr) {
+ qWarning("Failed to find vkGetInstanceProcAddr");
+ return;
+ }
+
+ m_vkCreateInstance = reinterpret_cast<PFN_vkCreateInstance>(m_vkGetInstanceProcAddr(VK_NULL_HANDLE, "vkCreateInstance"));
+ if (!m_vkCreateInstance) {
+ qWarning("Failed to find vkCreateInstance");
+ return;
+ }
+ m_vkEnumerateInstanceLayerProperties = reinterpret_cast<PFN_vkEnumerateInstanceLayerProperties>(
+ m_vkGetInstanceProcAddr(VK_NULL_HANDLE, "vkEnumerateInstanceLayerProperties"));
+ if (!m_vkEnumerateInstanceLayerProperties) {
+ qWarning("Failed to find vkEnumerateInstanceLayerProperties");
+ return;
+ }
+ m_vkEnumerateInstanceExtensionProperties = reinterpret_cast<PFN_vkEnumerateInstanceExtensionProperties>(
+ m_vkGetInstanceProcAddr(VK_NULL_HANDLE, "vkEnumerateInstanceExtensionProperties"));
+ if (!m_vkEnumerateInstanceExtensionProperties) {
+ qWarning("Failed to find vkEnumerateInstanceExtensionProperties");
+ return;
+ }
+
+ // Do not rely on non-1.0 header typedefs here.
+ typedef VkResult (VKAPI_PTR *T_enumerateInstanceVersion)(uint32_t* pApiVersion);
+ // Determine instance-level version as described in the Vulkan 1.2 spec.
+ T_enumerateInstanceVersion enumerateInstanceVersion = reinterpret_cast<T_enumerateInstanceVersion>(
+ m_vkGetInstanceProcAddr(VK_NULL_HANDLE, "vkEnumerateInstanceVersion"));
+ if (enumerateInstanceVersion) {
+ uint32_t ver = 0;
+ if (enumerateInstanceVersion(&ver) == VK_SUCCESS) {
+ m_supportedApiVersion = QVersionNumber(VK_VERSION_MAJOR(ver),
+ VK_VERSION_MINOR(ver),
+ VK_VERSION_PATCH(ver));
+ } else {
+ m_supportedApiVersion = QVersionNumber(1, 0, 0);
+ }
+ } else {
+ // Vulkan 1.0
+ m_supportedApiVersion = QVersionNumber(1, 0, 0);
+ }
+
+ uint32_t layerCount = 0;
+ m_vkEnumerateInstanceLayerProperties(&layerCount, nullptr);
+ if (layerCount) {
+ QList<VkLayerProperties> layerProps(layerCount);
+ m_vkEnumerateInstanceLayerProperties(&layerCount, layerProps.data());
+ m_supportedLayers.reserve(layerCount);
+ for (const VkLayerProperties &p : std::as_const(layerProps)) {
+ QVulkanLayer layer;
+ layer.name = p.layerName;
+ layer.version = p.implementationVersion;
+ layer.specVersion = QVersionNumber(VK_VERSION_MAJOR(p.specVersion),
+ VK_VERSION_MINOR(p.specVersion),
+ VK_VERSION_PATCH(p.specVersion));
+ layer.description = p.description;
+ m_supportedLayers.append(layer);
+ }
+ }
+ qCDebug(lcPlatVk) << "Supported Vulkan instance layers:" << m_supportedLayers;
+
+ uint32_t extCount = 0;
+ m_vkEnumerateInstanceExtensionProperties(nullptr, &extCount, nullptr);
+ if (extCount) {
+ QList<VkExtensionProperties> extProps(extCount);
+ m_vkEnumerateInstanceExtensionProperties(nullptr, &extCount, extProps.data());
+ m_supportedExtensions.reserve(extCount);
+ for (const VkExtensionProperties &p : std::as_const(extProps)) {
+ QVulkanExtension ext;
+ ext.name = p.extensionName;
+ ext.version = p.specVersion;
+ m_supportedExtensions.append(ext);
+ }
+ }
+ qDebug(lcPlatVk) << "Supported Vulkan instance extensions:" << m_supportedExtensions;
+}
+
+QVulkanInfoVector<QVulkanLayer> QBasicPlatformVulkanInstance::supportedLayers() const
+{
+ return m_supportedLayers;
+}
+
+QVulkanInfoVector<QVulkanExtension> QBasicPlatformVulkanInstance::supportedExtensions() const
+{
+ return m_supportedExtensions;
+}
+
+QVersionNumber QBasicPlatformVulkanInstance::supportedApiVersion() const
+{
+ return m_supportedApiVersion;
+}
+
+void QBasicPlatformVulkanInstance::initInstance(QVulkanInstance *instance, const QByteArrayList &extraExts)
+{
+ if (!m_vkGetInstanceProcAddr) {
+ qWarning("initInstance: No Vulkan library available");
+ return;
+ }
+
+ m_vkInst = instance->vkInstance(); // when non-null we are adopting an existing instance
+
+ QVulkanInstance::Flags flags = instance->flags();
+ m_enabledLayers = instance->layers();
+ m_enabledExtensions = instance->extensions();
+
+ if (!m_vkInst) {
+ VkApplicationInfo appInfo = {};
+ appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
+ QByteArray appName = QCoreApplication::applicationName().toUtf8();
+ appInfo.pApplicationName = appName.constData();
+ const QVersionNumber apiVersion = instance->apiVersion();
+ if (!apiVersion.isNull()) {
+ appInfo.apiVersion = VK_MAKE_VERSION(apiVersion.majorVersion(),
+ apiVersion.minorVersion(),
+ apiVersion.microVersion());
+ }
+
+ m_enabledExtensions.append("VK_KHR_surface");
+ if (!flags.testFlag(QVulkanInstance::NoPortabilityDrivers))
+ m_enabledExtensions.append("VK_KHR_portability_enumeration");
+ if (!flags.testFlag(QVulkanInstance::NoDebugOutputRedirect))
+ m_enabledExtensions.append("VK_EXT_debug_utils");
+
+ for (const QByteArray &ext : extraExts)
+ m_enabledExtensions.append(ext);
+
+ QByteArray envExts = qgetenv("QT_VULKAN_INSTANCE_EXTENSIONS");
+ if (!envExts.isEmpty()) {
+ QByteArrayList envExtList = envExts.split(';');
+ for (auto ext : m_enabledExtensions)
+ envExtList.removeAll(ext);
+ m_enabledExtensions.append(envExtList);
+ }
+
+ QByteArray envLayers = qgetenv("QT_VULKAN_INSTANCE_LAYERS");
+ if (!envLayers.isEmpty()) {
+ QByteArrayList envLayerList = envLayers.split(';');
+ for (auto ext : m_enabledLayers)
+ envLayerList.removeAll(ext);
+ m_enabledLayers.append(envLayerList);
+ }
+
+ // No clever stuff with QSet and friends: the order for layers matters
+ // and the user-provided order must be kept.
+ for (int i = 0; i < m_enabledLayers.size(); ++i) {
+ const QByteArray &layerName(m_enabledLayers[i]);
+ if (!m_supportedLayers.contains(layerName))
+ m_enabledLayers.removeAt(i--);
+ }
+ qDebug(lcPlatVk) << "Enabling Vulkan instance layers:" << m_enabledLayers;
+ for (int i = 0; i < m_enabledExtensions.size(); ++i) {
+ const QByteArray &extName(m_enabledExtensions[i]);
+ if (!m_supportedExtensions.contains(extName))
+ m_enabledExtensions.removeAt(i--);
+ }
+ qDebug(lcPlatVk) << "Enabling Vulkan instance extensions:" << m_enabledExtensions;
+
+ VkInstanceCreateInfo instInfo = {};
+ instInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
+ instInfo.pApplicationInfo = &appInfo;
+ if (!flags.testFlag(QVulkanInstance::NoPortabilityDrivers)) {
+ // With old Vulkan SDKs setting a non-zero flags gives a validation error.
+ // Whereas from 1.3.216 on the portability bit is required for MoltenVK to function.
+ // Hence the version check.
+ if (m_supportedApiVersion >= QVersionNumber(1, 3, 216))
+ instInfo.flags |= 0x00000001; // VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR
+ }
+
+ QList<const char *> layerNameVec;
+ for (const QByteArray &ba : std::as_const(m_enabledLayers))
+ layerNameVec.append(ba.constData());
+ if (!layerNameVec.isEmpty()) {
+ instInfo.enabledLayerCount = layerNameVec.size();
+ instInfo.ppEnabledLayerNames = layerNameVec.constData();
+ }
+
+ QList<const char *> extNameVec;
+ for (const QByteArray &ba : std::as_const(m_enabledExtensions))
+ extNameVec.append(ba.constData());
+ if (!extNameVec.isEmpty()) {
+ instInfo.enabledExtensionCount = extNameVec.size();
+ instInfo.ppEnabledExtensionNames = extNameVec.constData();
+ }
+
+ m_errorCode = m_vkCreateInstance(&instInfo, nullptr, &m_vkInst);
+ if (m_errorCode != VK_SUCCESS || !m_vkInst) {
+ qWarning("Failed to create Vulkan instance: %d", m_errorCode);
+ return;
+ }
+
+ m_vkDestroyInstance = reinterpret_cast<PFN_vkDestroyInstance>(m_vkGetInstanceProcAddr(m_vkInst, "vkDestroyInstance"));
+ if (!m_vkDestroyInstance) {
+ qWarning("Failed to find vkDestroyInstance");
+ m_vkInst = VK_NULL_HANDLE;
+ return;
+ }
+
+ m_ownsVkInst = true;
+ }
+
+ m_getPhysDevSurfaceSupport = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfaceSupportKHR>(
+ m_vkGetInstanceProcAddr(m_vkInst, "vkGetPhysicalDeviceSurfaceSupportKHR"));
+ if (!m_getPhysDevSurfaceSupport)
+ qWarning("Failed to find vkGetPhysicalDeviceSurfaceSupportKHR");
+
+ m_destroySurface = reinterpret_cast<PFN_vkDestroySurfaceKHR>(
+ m_vkGetInstanceProcAddr(m_vkInst, "vkDestroySurfaceKHR"));
+ if (!m_destroySurface)
+ qWarning("Failed to find vkDestroySurfaceKHR");
+
+ if (!flags.testFlag(QVulkanInstance::NoDebugOutputRedirect))
+ setupDebugOutput();
+}
+
+bool QBasicPlatformVulkanInstance::isValid() const
+{
+ return m_vkInst != VK_NULL_HANDLE;
+}
+
+VkResult QBasicPlatformVulkanInstance::errorCode() const
+{
+ return m_errorCode;
+}
+
+VkInstance QBasicPlatformVulkanInstance::vkInstance() const
+{
+ return m_vkInst;
+}
+
+QByteArrayList QBasicPlatformVulkanInstance::enabledLayers() const
+{
+ return m_enabledLayers;
+}
+
+QByteArrayList QBasicPlatformVulkanInstance::enabledExtensions() const
+{
+ return m_enabledExtensions;
+}
+
+PFN_vkVoidFunction QBasicPlatformVulkanInstance::getInstanceProcAddr(const char *name)
+{
+ if (!name)
+ return nullptr;
+
+ const bool needsNullInstance = !strcmp(name, "vkEnumerateInstanceLayerProperties")
+ || !strcmp(name, "vkEnumerateInstanceExtensionProperties");
+
+ return m_vkGetInstanceProcAddr(needsNullInstance ? 0 : m_vkInst, name);
+}
+
+bool QBasicPlatformVulkanInstance::supportsPresent(VkPhysicalDevice physicalDevice,
+ uint32_t queueFamilyIndex,
+ QWindow *window)
+{
+ if (!m_getPhysDevSurfaceSupport)
+ return true;
+
+ VkSurfaceKHR surface = QVulkanInstance::surfaceForWindow(window);
+ VkBool32 supported = false;
+ m_getPhysDevSurfaceSupport(physicalDevice, queueFamilyIndex, surface, &supported);
+
+ return supported;
+}
+
+void QBasicPlatformVulkanInstance::setDebugFilters(const QList<QVulkanInstance::DebugFilter> &filters)
+{
+ m_debugFilters = filters;
+}
+
+void QBasicPlatformVulkanInstance::setDebugUtilsFilters(const QList<QVulkanInstance::DebugUtilsFilter> &filters)
+{
+ m_debugUtilsFilters = filters;
+}
+
+void QBasicPlatformVulkanInstance::destroySurface(VkSurfaceKHR surface) const
+{
+ if (m_destroySurface && surface)
+ m_destroySurface(m_vkInst, surface, nullptr);
+}
+
+#ifdef VK_EXT_debug_utils
+static VKAPI_ATTR VkBool32 VKAPI_CALL defaultDebugCallbackFunc(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
+ VkDebugUtilsMessageTypeFlagsEXT messageType,
+ const VkDebugUtilsMessengerCallbackDataEXT *pCallbackData,
+ void *pUserData)
+{
+ QBasicPlatformVulkanInstance *self = static_cast<QBasicPlatformVulkanInstance *>(pUserData);
+
+ // legacy filters
+ for (QVulkanInstance::DebugFilter filter : *self->debugFilters()) {
+ // As per docs in qvulkaninstance.cpp we pass object, messageCode,
+ // pMessage to the callback with the legacy signature.
+ uint64_t object = 0;
+ if (pCallbackData->objectCount > 0)
+ object = pCallbackData->pObjects[0].objectHandle;
+ if (filter(0, VK_DEBUG_REPORT_OBJECT_TYPE_UNKNOWN_EXT, object, 0,
+ pCallbackData->messageIdNumber, "", pCallbackData->pMessage))
+ {
+ return VK_FALSE;
+ }
+ }
+
+ // filters with new signature
+ for (QVulkanInstance::DebugUtilsFilter filter : *self->debugUtilsFilters()) {
+ QVulkanInstance::DebugMessageSeverityFlags severity;
+ if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT)
+ severity |= QVulkanInstance::VerboseSeverity;
+ if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT)
+ severity |= QVulkanInstance::InfoSeverity;
+ if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT)
+ severity |= QVulkanInstance::WarningSeverity;
+ if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT)
+ severity |= QVulkanInstance::ErrorSeverity;
+ QVulkanInstance::DebugMessageTypeFlags type;
+ if (messageType & VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT)
+ type |= QVulkanInstance::GeneralMessage;
+ if (messageType & VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT)
+ type |= QVulkanInstance::ValidationMessage;
+ if (messageType & VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT)
+ type |= QVulkanInstance::PerformanceMessage;
+ if (filter(severity, type, pCallbackData))
+ return VK_FALSE;
+ }
+
+ // not categorized, just route to plain old qDebug
+ qDebug("vkDebug: %s", pCallbackData->pMessage);
+
+ return VK_FALSE;
+}
+#endif
+
+void QBasicPlatformVulkanInstance::setupDebugOutput()
+{
+#ifdef VK_EXT_debug_utils
+ if (!m_enabledExtensions.contains("VK_EXT_debug_utils"))
+ return;
+
+ PFN_vkCreateDebugUtilsMessengerEXT vkCreateDebugUtilsMessengerEXT = reinterpret_cast<PFN_vkCreateDebugUtilsMessengerEXT>(
+ m_vkGetInstanceProcAddr(m_vkInst, "vkCreateDebugUtilsMessengerEXT"));
+
+ m_vkDestroyDebugUtilsMessengerEXT = reinterpret_cast<PFN_vkDestroyDebugUtilsMessengerEXT>(
+ m_vkGetInstanceProcAddr(m_vkInst, "vkDestroyDebugUtilsMessengerEXT"));
+
+ VkDebugUtilsMessengerCreateInfoEXT messengerInfo = {};
+ messengerInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
+ messengerInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT
+ | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
+ messengerInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT
+ | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT
+ | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
+ messengerInfo.pfnUserCallback = defaultDebugCallbackFunc;
+ messengerInfo.pUserData = this;
+ VkResult err = vkCreateDebugUtilsMessengerEXT(m_vkInst, &messengerInfo, nullptr, &m_debugMessenger);
+ if (err != VK_SUCCESS)
+ qWarning("Failed to create debug report callback: %d", err);
+#endif
+}
+
+QT_END_NAMESPACE