/**************************************************************************** ** ** Copyright (C) 2017 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtGui module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qvulkaninstance.h" #include #include #include #include #include QT_BEGIN_NAMESPACE /*! \class QVulkanInstance \since 5.10 \inmodule QtGui \brief The QVulkanInstance class represents a native Vulkan instance, enabling Vulkan rendering onto a QSurface. \l{https://www.khronos.org/vulkan/}{Vulkan} is a cross-platform, explicit graphics and compute API. This class provides support for loading a Vulkan library and creating an \c instance in a cross-platform manner. For an introduction on Vulkan instances, refer \l{https://www.khronos.org/registry/vulkan/specs/1.0/html/vkspec.html#initialization-instances}{to section 3.2 of the specification}. \note Platform-specific support for Vulkan instances and windows with Vulkan-capable surfaces is provided by the various platform plugins. Not all of them will support Vulkan, however. When running on such a platform, create() will fail and always return \c false. \note Vulkan support may get automatically disabled for a given Qt build due to not having the necessary Vulkan headers available at build time. When this is the case, and the output of \c configure indicates Vulkan support is disabled, the QVulkan* classes will be unavailable. \note Some functions changed their signature between the various Vulkan header revisions. When building Qt and only headers with the old, conflicting signatures are present in a system, Vulkan support will get disabled. It is recommended to use headers from Vulkan 1.0.39 or newer. \section1 Initialization Similarly to QOpenGLContext, any actual Vulkan instance creation happens only when calling create(). This allows using QVulkanInstance as a plain member variable while retaining control over when to perform initialization. Querying the supported instance-level layers and extensions is possible by calling supportedLayers() and supportedExtensions(). These ensure the Vulkan library is loaded, and can therefore be called safely before create() as well. Instances store per-application Vulkan state and creating a \c VkInstance object initializes the Vulkan library. In practice there will typically be a single instance constructed early on in main(). The object then stays alive until exiting the application. Every Vulkan-based QWindow must be associated with a QVulkanInstance by calling QWindow::setVulkanInstance(). Thus a typical application pattern is the following: \code int main(int argc, char **argv) { QGuiApplication app(argc, argv); QVulkanInstance inst; if (!inst.create()) return 1; ... window->setVulkanInstance(&inst); window->show(); return app.exec(); } \endcode \section1 Configuration QVulkanInstance automatically enables the minimum set of extensions it needs on the newly created instance. In practice this means the \c{VK_KHR_*_surface} family of extensions. By default Vulkan debug output, for example messages from the validation layers, is routed to qDebug(). This can be disabled by passing the flag \c NoDebugOutputRedirect to setFlags() \e before invoking create(). To enable additional layers and extensions, provide the list via setLayers() and setExtensions() \e before invoking create(). When a given layer or extension is not reported as available from the instance, the request is ignored. After a successful call to create(), the values returned from functions like layers() and extensions() reflect the actual enabled layers and extensions. When necessary, for example to avoid requesting extensions that conflict and thus would fail the Vulkan instance creation, the list of actually supported layers and extensions can be examined via supportedLayers() and supportedExtensions() before calling create(). For example, to enable the standard validation layers, one could do the following: \code QVulkanInstance inst; // Enable validation layer, if supported. Messages go to qDebug by default. inst.setLayers(QByteArrayList() << "VK_LAYER_LUNARG_standard_validation"); bool ok = inst.create(); if (!ok) ... // Vulkan not available if (!inst.layers().contains("VK_LAYER_LUNARG_standard_validation")) ... // validation layer not available \endcode Or, alternatively, to make decisions before attempting to create a Vulkan instance: \code QVulkanInstance inst; if (inst.supportedLayers().contains("VK_LAYER_LUNARG_standard_validation")) ... bool ok = inst.create(); ... \endcode \section1 Adopting an Existing Instance By default QVulkanInstance creates a new Vulkan instance. When working with external engines and renderers, this may sometimes not be desirable. When there is a \c VkInstance handle already available, call setVkInstance() before invoking create(). This way no additional instances will get created, and QVulkanInstance will not own the handle. \note It is up to the component creating the external instance to ensure the necessary extensions are enabled on it. These are: \c{VK_KHR_surface}, the WSI-specific \c{VK_KHR_*_surface} that is appropriate for the platform in question, and \c{VK_EXT_debug_report} in case QVulkanInstance's debug output redirection is desired. \section1 Accessing Core Vulkan Commands To access the \c VkInstance handle the QVulkanInstance wraps, call vkInstance(). To resolve Vulkan functions, call getInstanceProcAddr(). For core Vulkan commands manual resolving is not necessary as they are provided via the QVulkanFunctions and QVulkanDeviceFunctions objects accessible via functions() and deviceFunctions(). \note QVulkanFunctions and QVulkanDeviceFunctions are generated from the Vulkan API XML specifications when building the Qt libraries. Therefore no documentation is provided for them. They contain the Vulkan 1.0 functions with the same signatures as described in the \l{https://www.khronos.org/registry/vulkan/specs/1.0/html/}{Vulkan API documentation}. \section1 Getting a Native Vulkan Surface for a Window The two common windowing system specific operations are getting a surface (a \c{VkSurfaceKHR} handle) for a window, and querying if a given queue family supports presenting to a given surface. To avoid WSI-specific bits in the applications, these are abstracted by QVulkanInstance and the underlying QPA layers. To create a Vulkan surface for a window, or retrieve an existing one, call surfaceForWindow(). Most platforms will only create the surface via \c{VK_KHR_*_surface} when first calling surfaceForWindow(), but there may be platform-specific variations in the internal behavior. Once created, subsequent calls to surfaceForWindow() just return the same handle. This fits the structure of typical Vulkan-enabled QWindow subclasses well. To query if a given queue family within a physical device can be used to perform presentation to a given surface, call supportsPresent(). This encapsulates both the generic \c vkGetPhysicalDeviceSurfaceSupportKHR and the WSI-specific \c{vkGetPhysicalDevice*PresentationSupportKHR} checks. \section1 Troubleshooting Besides returning \c false from create() or \c 0 from surfaceForWindow(), critical errors will also get printed to the debug output via qWarning(). Additional logging can be requested by enabling debug output for the logging category \c{qt.vulkan}. The actual Vulkan error code from instance creation can be retrieved by calling errorCode() after a failing create(). In some special cases it may be necessary to override the Vulkan library name. This can be achieved by setting the \c{QT_VULKAN_LIB} environment variable. \section1 Example The following is the basic outline of creating a Vulkan-capable QWindow: \code class VulkanWindow : public QWindow { public: VulkanWindow() { setSurfaceType(VulkanSurface); } void exposeEvent(QExposeEvent *) { if (isExposed()) { if (!m_initialized) { m_initialized = true; // initialize device, swapchain, etc. QVulkanInstance *inst = vulkanInstance(); QVulkanFunctions *f = inst->functions(); uint32_t devCount = 0; f->vkEnumeratePhysicalDevices(inst->vkInstance(), &devCount, nullptr); ... // build the first frame render(); } } } bool event(QEvent *e) { if (e->type == QEvent::UpdateRequest) render(); return QWindow::event(e); } void render() { ... requestUpdate(); // render continuously } private: bool m_initialized = false; }; int main(int argc, char **argv) { QGuiApplication app(argc, argv); QVulkanInstance inst; if (!inst.create()) { qWarning("Vulkan not available"); return 1; } VulkanWindow window; window.showMaximized(); return app.exec(); } \endcode \note In addition to expose, a well-behaving window implementation will also have to take care of additional events like resize and QPlatformSurfaceEvent in order to ensure proper management of the swap chain. Additionally, some platforms may require releasing resources when not being exposed anymore. \section1 Using C++ Bindings for Vulkan Combining Qt's Vulkan enablers with a C++ Vulkan wrapper, for example \l{https://github.com/KhronosGroup/Vulkan-Hpp}{Vulkan-Hpp}, is possible as well. The pre-requisite here is that the C++ layer must be able to adopt native handles (VkInstance, VkSurfaceKHR) in its classes without taking ownership (since the ownership stays with QVulkanInstance and QWindow). Consider also the following: \list \li Some wrappers require exception support to be enabled. Qt does not use exceptions. To enable exceptions for the application, add \c{CONFIG += exceptions} to the \c{.pro} file. \li Some wrappers call Vulkan functions directly, assuming \c{vulkan.h} provides prototypes and the application links to a Vulkan library exporting all necessary symbols. Qt may not directly link to a Vulkan library. Therefore, on some platforms it may be necessary to add \c{LIBS += -lvulkan} or similar in the application's \c{.pro} file. \li The headers for the QVulkan classes may include \c{vulkan.h} with \c{VK_NO_PROTOTYPES} enabled. This can cause issues in C++ wrapper headers that rely on the prototypes. Hence in application code it may be necessary to include \c{vulkan.hpp} or similar before any of the QVulkan headers. \endlist \sa QVulkanFunctions, QSurface::SurfaceType */ /*! \enum QVulkanInstance::Flag \since 5.10 This enum describes the flags that can be passed to setFlags(). These control the behavior of create(). \value NoDebugOutputRedirect Disables Vulkan debug output (\c{VK_EXT_debug_report}) redirection to qDebug. */ class QVulkanInstancePrivate { public: QVulkanInstancePrivate(QVulkanInstance *q) : q_ptr(q), vkInst(VK_NULL_HANDLE), flags(0), errorCode(VK_SUCCESS) { } ~QVulkanInstancePrivate() { reset(); } bool ensureVulkan(); void reset(); QVulkanInstance *q_ptr; QScopedPointer platformInst; VkInstance vkInst; QVulkanInstance::Flags flags; QByteArrayList layers; QByteArrayList extensions; QVersionNumber apiVersion; VkResult errorCode; QScopedPointer funcs; QHash deviceFuncs; }; bool QVulkanInstancePrivate::ensureVulkan() { if (!platformInst) { platformInst.reset(QGuiApplicationPrivate::platformIntegration()->createPlatformVulkanInstance(q_ptr)); if (!platformInst) { qWarning("QVulkanInstance: Failed to initialize Vulkan"); return false; } } return true; } void QVulkanInstancePrivate::reset() { qDeleteAll(deviceFuncs); deviceFuncs.clear(); funcs.reset(); platformInst.reset(); vkInst = VK_NULL_HANDLE; errorCode = VK_SUCCESS; } /*! Constructs a new instance. \note No Vulkan initialization is performed in the constructor. */ QVulkanInstance::QVulkanInstance() : d_ptr(new QVulkanInstancePrivate(this)) { } /*! Destructor. \note current() will return \c nullptr once the instance is destroyed. */ QVulkanInstance::~QVulkanInstance() { destroy(); } /*! \class QVulkanLayer \brief Represents information about a Vulkan layer. */ /*! \variable QVulkanLayer::name \brief The name of the layer. */ /*! \variable QVulkanLayer::version \brief The version of the layer. This is an integer, increasing with each backward compatible change. */ /*! \variable QVulkanLayer::specVersion \brief The Vulkan version the layer was written against. */ /*! \variable QVulkanLayer::description \brief The description of the layer. */ /*! \fn bool operator==(const QVulkanLayer &lhs, const QVulkanLayer &rhs) \since 5.10 \relates QVulkanLayer Returns \c true if Vulkan layers \a lhs and \a rhs have the same name, version, and spec version. */ /*! \fn bool operator!=(const QVulkanLayer &lhs, const QVulkanLayer &rhs) \since 5.10 \relates QVulkanLayer Returns \c true if Vulkan layers \a lhs and \a rhs have different name, version, or spec version. */ /*! \fn uint qHash(const QVulkanLayer &key, uint seed) \since 5.10 \relates QVulkanLayer Returns the hash value for the \a key, using \a seed to seed the calculation. */ /*! \class QVulkanExtension \brief Represents information about a Vulkan extension. */ /*! \variable QVulkanExtension::name \brief The name of the extension. */ /*! \variable QVulkanExtension::version \brief The version of the extension. This is an integer, increasing with each backward compatible change. */ /*! \fn bool operator==(const QVulkanExtension &lhs, const QVulkanExtension &rhs) \since 5.10 \relates QVulkanExtension Returns \c true if Vulkan extensions \a lhs and \a rhs are have the same name and version. */ /*! \fn bool operator!=(const QVulkanExtension &lhs, const QVulkanExtension &rhs) \since 5.10 \relates QVulkanExtension Returns \c true if Vulkan extensions \a lhs and \a rhs are have different name or version. */ /*! \fn uint qHash(const QVulkanExtension &key, uint seed) \since 5.10 \relates QVulkanExtension Returns the hash value for the \a key, using \a seed to seed the calculation. */ /*! \class QVulkanInfoVector \brief A specialized QVector for QVulkanLayer and QVulkanExtension. */ /*! \fn bool QVulkanInfoVector::contains(const QByteArray &name) const \return true if the vector contains a layer or extension with the given \a name. */ /*! \fn bool QVulkanInfoVector::contains(const QByteArray &name, int minVersion) const \return true if the vector contains a layer or extension with the given \a name and a version same as or newer than \a minVersion. */ /*! \return the list of supported instance-level layers. \note This function can be called before create(). */ QVulkanInfoVector QVulkanInstance::supportedLayers() { return d_ptr->ensureVulkan() ? d_ptr->platformInst->supportedLayers() : QVulkanInfoVector(); } /*! \return the list of supported instance-level extensions. \note This function can be called before create(). */ QVulkanInfoVector QVulkanInstance::supportedExtensions() { return d_ptr->ensureVulkan() ? d_ptr->platformInst->supportedExtensions() : QVulkanInfoVector(); } /*! Makes QVulkanInstance adopt an existing VkInstance handle instead of creating a new one. \note \a existingVkInstance must have at least \c{VK_KHR_surface} and the appropriate WSI-specific \c{VK_KHR_*_surface} extensions enabled. To ensure debug output redirection is functional, \c{VK_EXT_debug_report} is needed as well. \note This function can only be called before create() and has no effect if called afterwards. */ void QVulkanInstance::setVkInstance(VkInstance existingVkInstance) { if (isValid()) { qWarning("QVulkanInstance already created; setVkInstance() has no effect"); return; } d_ptr->vkInst = existingVkInstance; } /*! Configures the behavior of create() based on the provided \a flags. \note This function can only be called before create() and has no effect if called afterwards. */ void QVulkanInstance::setFlags(Flags flags) { if (isValid()) { qWarning("QVulkanInstance already created; setFlags() has no effect"); return; } d_ptr->flags = flags; } /*! Specifies the list of instance \a layers to enable. It is safe to specify unsupported layers as well because these get ignored when not supported at run time. \note This function can only be called before create() and has no effect if called afterwards. */ void QVulkanInstance::setLayers(const QByteArrayList &layers) { if (isValid()) { qWarning("QVulkanInstance already created; setLayers() has no effect"); return; } d_ptr->layers = layers; } /*! Specifies the list of additional instance \a extensions to enable. It is safe to specify unsupported extensions as well because these get ignored when not supported at run time. The surface-related extensions required by Qt will always be added automatically, no need to include them in this list. \note This function can only be called before create() and has no effect if called afterwards. */ void QVulkanInstance::setExtensions(const QByteArrayList &extensions) { if (isValid()) { qWarning("QVulkanInstance already created; setExtensions() has no effect"); return; } d_ptr->extensions = extensions; } /*! Specifies the Vulkan API against which the application expects to run. By default no \a vulkanVersion is specified, and so no version check is performed during Vulkan instance creation. \note This function can only be called before create() and has no effect if called afterwards. */ void QVulkanInstance::setApiVersion(const QVersionNumber &vulkanVersion) { if (isValid()) { qWarning("QVulkanInstance already created; setApiVersion() has no effect"); return; } d_ptr->apiVersion = vulkanVersion; } /*! Initializes the Vulkan library and creates a new or adopts and existing Vulkan instance. \return true if successful, false on error or when Vulkan is not supported. When successful, the pointer to this QVulkanInstance is retrievable via the static function current(). The Vulkan instance and library is available as long as this QVulkanInstance exists, or until destroy() is called. */ bool QVulkanInstance::create() { if (isValid()) destroy(); if (!d_ptr->ensureVulkan()) return false; d_ptr->platformInst->createOrAdoptInstance(); if (d_ptr->platformInst->isValid()) { d_ptr->vkInst = d_ptr->platformInst->vkInstance(); d_ptr->layers = d_ptr->platformInst->enabledLayers(); d_ptr->extensions = d_ptr->platformInst->enabledExtensions(); d_ptr->errorCode = VK_SUCCESS; d_ptr->funcs.reset(new QVulkanFunctions(this)); return true; } qWarning("Failed to create platform Vulkan instance"); if (d_ptr->platformInst) { d_ptr->errorCode = d_ptr->platformInst->errorCode(); d_ptr->platformInst.reset(); } else { d_ptr->errorCode = VK_NOT_READY; } return false; } /*! Destroys the underlying platform instance, thus destroying the VkInstance (when owned). The QVulkanInstance object is still reusable by calling create() again. */ void QVulkanInstance::destroy() { d_ptr->reset(); } /*! \return true if create() was successful and the instance is valid. */ bool QVulkanInstance::isValid() const { return d_ptr->platformInst && d_ptr->platformInst->isValid(); } /*! \return the Vulkan error code after an unsuccessful create(), \c VK_SUCCESS otherwise. The value is typically the return value from vkCreateInstance() (when creating a new Vulkan instance instead of adopting an existing one), but may also be \c VK_NOT_READY if the platform plugin does not support Vulkan. */ VkResult QVulkanInstance::errorCode() const { return d_ptr->errorCode; } /*! \return the VkInstance handle this QVulkanInstance wraps, or \c null if create() has not yet been successfully called and no existing instance has been provided via setVkInstance(). */ VkInstance QVulkanInstance::vkInstance() const { return d_ptr->vkInst; } /*! \return the requested flags. */ QVulkanInstance::Flags QVulkanInstance::flags() const { return d_ptr->flags; } /*! \return the enabled instance layers, if create() was called and was successful. The requested layers otherwise. */ QByteArrayList QVulkanInstance::layers() const { return d_ptr->layers; } /*! \return the enabled instance extensions, if create() was called and was successful. The requested extensions otherwise. */ QByteArrayList QVulkanInstance::extensions() const { return d_ptr->extensions; } /*! \return the requested Vulkan API version against which the application expects to run, or a null version number if setApiVersion() was not called before create(). */ QVersionNumber QVulkanInstance::apiVersion() const { return d_ptr->apiVersion; } /*! Resolves the Vulkan function with the given \a name. For core Vulkan commands prefer using the function wrappers retrievable from functions() and deviceFunctions() instead. */ PFN_vkVoidFunction QVulkanInstance::getInstanceProcAddr(const char *name) { // The return value is PFN_vkVoidFunction instead of QFunctionPointer or // similar because on some platforms honoring VKAPI_PTR is important. return d_ptr->platformInst->getInstanceProcAddr(name); } /*! \return the platform Vulkan instance corresponding to this QVulkanInstance. \internal */ QPlatformVulkanInstance *QVulkanInstance::handle() const { return d_ptr->platformInst.data(); } /*! \return the corresponding QVulkanFunctions object that exposes the core Vulkan command set, excluding device level functions, and is guaranteed to be functional cross-platform. \note The returned object is owned and managed by the QVulkanInstance. Do not destroy or alter it. \sa deviceFunctions() */ QVulkanFunctions *QVulkanInstance::functions() const { return d_ptr->funcs.data(); } /*! \return the QVulkanDeviceFunctions object that exposes the device level core Vulkan command set and is guaranteed to be functional cross-platform. \note The Vulkan functions in the returned object must only be called with \a device or a child object (VkQueue, VkCommandBuffer) of \a device as their first parameter. This is because these functions are resolved via \l{https://www.khronos.org/registry/vulkan/specs/1.0/man/html/vkGetDeviceProcAddr.html}{vkGetDeviceProcAddr} in order to avoid the potential overhead of internal dispatching. \note The returned object is owned and managed by the QVulkanInstance. Do not destroy or alter it. \note The object is cached so calling this function with the same \a device again is a cheap operation. However, when the device gets destroyed, it is up to the application to notify the QVulkanInstance by calling resetDeviceFunctions(). \sa functions(), resetDeviceFunctions() */ QVulkanDeviceFunctions *QVulkanInstance::deviceFunctions(VkDevice device) { QVulkanDeviceFunctions *&f = d_ptr->deviceFuncs[device]; if (!f) f = new QVulkanDeviceFunctions(this, device); return f; } /*! Invalidates and destroys the QVulkanDeviceFunctions object for the given \a device. This function must be called when a VkDevice, for which deviceFunctions() was called, gets destroyed while the application intends to continue running, possibly creating a new logical Vulkan device later on. There is no need to call this before destroying the QVulkanInstance since clean up is then performed automatically. \sa deviceFunctions() */ void QVulkanInstance::resetDeviceFunctions(VkDevice device) { QVulkanDeviceFunctions *&f = d_ptr->deviceFuncs[device]; delete f; f = nullptr; } /*! Creates or retrieves the already existing \c{VkSurfaceKHR} handle for the given \a window. \return the Vulkan surface handle or 0 when failed. */ VkSurfaceKHR QVulkanInstance::surfaceForWindow(QWindow *window) { QPlatformNativeInterface *nativeInterface = qGuiApp->platformNativeInterface(); // VkSurfaceKHR is non-dispatchable and maps to a pointer on x64 and a uint64 on x86. // Therefore a pointer is returned from the platform plugin, not the value itself. void *p = nativeInterface->nativeResourceForWindow(QByteArrayLiteral("vkSurface"), window); return p ? *static_cast(p) : 0; } /*! \return true if the queue family with \a queueFamilyIndex within the \a physicalDevice supports presenting to \a window. Call this function when examining the queues of a given Vulkan device, in order to decide which queue can be used for performing presentation. */ bool QVulkanInstance::supportsPresent(VkPhysicalDevice physicalDevice, uint32_t queueFamilyIndex, QWindow *window) { return d_ptr->platformInst->supportsPresent(physicalDevice, queueFamilyIndex, window); } /*! This function should be called by the application's renderer after queuing a present operation for \a window. While on some platforms this will be a no-op, some may perform windowing system dependent synchronization. For example, on X11 this will update \c{_NET_WM_SYNC_REQUEST_COUNTER}. */ void QVulkanInstance::presentQueued(QWindow *window) { d_ptr->platformInst->presentQueued(window); } #ifndef QT_NO_DEBUG_STREAM QDebug operator<<(QDebug dbg, const QVulkanLayer &layer) { QDebugStateSaver saver(dbg); dbg.nospace() << "QVulkanLayer(" << layer.name << " " << layer.version << " " << layer.specVersion << " " << layer.description << ")"; return dbg; } QDebug operator<<(QDebug dbg, const QVulkanExtension &extension) { QDebugStateSaver saver(dbg); dbg.nospace() << "QVulkanExtension(" << extension.name << " " << extension.version << ")"; return dbg; } #endif QT_END_NAMESPACE