macOS: Experimental Vulkan support via MoltenVK

Add support for QSurface::VulkanSurface and QVulkanWindow.

Usage:
  1) Build MoltenVK according to instructions
  2) Configure Qt: ./configure -I /path/to/MoltenVK/Package/Release/MoltenVK/include
  3) export QT_VULKAN_LIB=/path/to/MoltenVK/Package/Release/MoltenVK/macOS/libMoltenVK.

Implement support for QSurface::VulkanSurface by enabling
layer mode for QNSView and then creating a CAMetalLayer,
which the MoltenVK translation layer can run on.

MoltenVK provides an implementation of the Vulcan API,
which means that the platform integration is similar
to other platforms: implement a QCocoaVulkanInstance
where we pass the QNSView instance to the vkCreateMacOSSurfaceMVK
Vulkan surface constructor function.

Using Vulkan directly without QVulkanWindow is possible, but not
tested.

We currently load libMoltenVK at run-time and use the
existing QT_VULKAN_LIB environment variable to set its
path. For deployment purposes it would be better to
link against MoltenVK.frameworkm, but this

Task-number: QTBUG-66966
Change-Id: I04ec6289c40b199dca9fed32902b5d2ad4e9c030
Reviewed-by: Morten Johan Sørvig <morten.sorvig@qt.io>
This commit is contained in:
Morten Johan Sørvig 2018-03-08 14:05:21 +01:00
parent 0b815aa2f8
commit 60457e6cd0
10 changed files with 261 additions and 7 deletions

View File

@ -11,4 +11,6 @@ device.dir_affix = $${device.sdk}
device.CONFIG = $${device.sdk} device.CONFIG = $${device.sdk}
device.deployment_identifier = $${device.sdk} device.deployment_identifier = $${device.sdk}
QMAKE_LIBS_VULKAN =
include(mac.conf) include(mac.conf)

View File

@ -72,10 +72,14 @@ HEADERS += qcocoaintegration.h \
qtConfig(opengl.*) { qtConfig(opengl.*) {
SOURCES += qcocoaglcontext.mm SOURCES += qcocoaglcontext.mm
HEADERS += qcocoaglcontext.h HEADERS += qcocoaglcontext.h
} }
qtConfig(vulkan) {
SOURCES += qcocoavulkaninstance.mm
HEADERS += qcocoavulkaninstance.h
}
RESOURCES += qcocoaresources.qrc RESOURCES += qcocoaresources.qrc
LIBS += -framework AppKit -framework CoreServices -framework Carbon -framework IOKit -framework QuartzCore -framework Metal -lcups LIBS += -framework AppKit -framework CoreServices -framework Carbon -framework IOKit -framework QuartzCore -framework Metal -lcups
@ -85,6 +89,8 @@ QT += \
accessibility_support-private clipboard_support-private theme_support-private \ accessibility_support-private clipboard_support-private theme_support-private \
fontdatabase_support-private graphics_support-private fontdatabase_support-private graphics_support-private
qtConfig(vulkan): QT += vulkan_support-private
CONFIG += no_app_extension_api_only CONFIG += no_app_extension_api_only
qtHaveModule(widgets) { qtHaveModule(widgets) {

View File

@ -51,6 +51,9 @@
#include "qcocoadrag.h" #include "qcocoadrag.h"
#include "qcocoaservices.h" #include "qcocoaservices.h"
#include "qcocoakeymapper.h" #include "qcocoakeymapper.h"
#if QT_CONFIG(vulkan)
#include "qcocoavulkaninstance.h"
#endif
#include <QtCore/QScopedPointer> #include <QtCore/QScopedPointer>
#include <qpa/qplatformintegration.h> #include <qpa/qplatformintegration.h>
@ -86,6 +89,11 @@ public:
QAbstractEventDispatcher *createEventDispatcher() const override; QAbstractEventDispatcher *createEventDispatcher() const override;
#if QT_CONFIG(vulkan)
QPlatformVulkanInstance *createPlatformVulkanInstance(QVulkanInstance *instance) const override;
QCocoaVulkanInstance *getCocoaVulkanInstance() const;
#endif
QCoreTextFontDatabase *fontDatabase() const override; QCoreTextFontDatabase *fontDatabase() const override;
QCocoaNativeInterface *nativeInterface() const override; QCocoaNativeInterface *nativeInterface() const override;
QPlatformInputContext *inputContext() const override; QPlatformInputContext *inputContext() const override;
@ -144,6 +152,9 @@ private:
QScopedPointer<QCocoaServices> mServices; QScopedPointer<QCocoaServices> mServices;
QScopedPointer<QCocoaKeyMapper> mKeyboardMapper; QScopedPointer<QCocoaKeyMapper> mKeyboardMapper;
#if QT_CONFIG(vulkan)
mutable QCocoaVulkanInstance *mCocoaVulkanInstance = nullptr;
#endif
QHash<QWindow *, NSToolbar *> mToolbars; QHash<QWindow *, NSToolbar *> mToolbars;
QList<QCocoaWindow *> m_popupWindowStack; QList<QCocoaWindow *> m_popupWindowStack;
}; };

View File

@ -379,6 +379,19 @@ QAbstractEventDispatcher *QCocoaIntegration::createEventDispatcher() const
return new QCocoaEventDispatcher; return new QCocoaEventDispatcher;
} }
#if QT_CONFIG(vulkan)
QPlatformVulkanInstance *QCocoaIntegration::createPlatformVulkanInstance(QVulkanInstance *instance) const
{
mCocoaVulkanInstance = new QCocoaVulkanInstance(instance);
return mCocoaVulkanInstance;
}
QCocoaVulkanInstance *QCocoaIntegration::getCocoaVulkanInstance() const
{
return mCocoaVulkanInstance;
}
#endif
QCoreTextFontDatabase *QCocoaIntegration::fontDatabase() const QCoreTextFontDatabase *QCocoaIntegration::fontDatabase() const
{ {
return mFontDb.data(); return mFontDb.data();

View File

@ -71,6 +71,10 @@
#include <AppKit/AppKit.h> #include <AppKit/AppKit.h>
#if QT_CONFIG(vulkan)
#include <MoltenVK/mvk_vulkan.h>
#endif
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
QCocoaNativeInterface::QCocoaNativeInterface() QCocoaNativeInterface::QCocoaNativeInterface()
@ -104,6 +108,11 @@ void *QCocoaNativeInterface::nativeResourceForWindow(const QByteArray &resourceS
#endif #endif
} else if (resourceString == "nswindow") { } else if (resourceString == "nswindow") {
return static_cast<QCocoaWindow *>(window->handle())->nativeWindow(); return static_cast<QCocoaWindow *>(window->handle())->nativeWindow();
#if QT_CONFIG(vulkan)
} else if (resourceString == "vkSurface") {
if (QVulkanInstance *instance = window->vulkanInstance())
return static_cast<QCocoaVulkanInstance *>(instance->handle())->createSurface(window);
#endif
} }
return nullptr; return nullptr;
} }

View File

@ -0,0 +1,74 @@
/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of plugins 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$
**
****************************************************************************/
#ifndef QCOCOAVULKANINSTANCE_H
#define QCOCOAVULKANINSTANCE_H
#include <QtCore/QLibrary>
#include <QtCore/QHash>
#include <QtVulkanSupport/private/qbasicvulkanplatforminstance_p.h>
#include <AppKit/AppKit.h>
#include <MoltenVK/mvk_vulkan.h>
QT_BEGIN_NAMESPACE
class QCocoaVulkanInstance : public QBasicPlatformVulkanInstance
{
public:
QCocoaVulkanInstance(QVulkanInstance *instance);
~QCocoaVulkanInstance();
void createOrAdoptInstance() override;
VkSurfaceKHR *createSurface(QWindow *window);
VkSurfaceKHR createSurface(NSView *view);
void destroySurface(VkSurfaceKHR surface);
private:
QVulkanInstance *m_instance = nullptr;
QLibrary m_lib;
VkSurfaceKHR m_nullSurface = nullptr;
PFN_vkCreateMacOSSurfaceMVK m_createSurface = nullptr;
PFN_vkDestroySurfaceKHR m_destroySurface = nullptr;
};
QT_END_NAMESPACE
#endif // QXCBVULKANINSTANCE_H

View File

@ -0,0 +1,119 @@
/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of plugins 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 "qcocoavulkaninstance.h"
#include "qcocoawindow.h"
QT_BEGIN_NAMESPACE
QCocoaVulkanInstance::QCocoaVulkanInstance(QVulkanInstance *instance)
: m_instance(instance)
{
if (qEnvironmentVariableIsSet("QT_VULKAN_LIB"))
m_lib.setFileName(QString::fromUtf8(qgetenv("QT_VULKAN_LIB")));
else
m_lib.setFileName(QStringLiteral("vulkan"));
if (!m_lib.load()) {
qWarning("Failed to load %s: %s", qPrintable(m_lib.fileName()), qPrintable(m_lib.errorString()));
return;
}
init(&m_lib);
}
QCocoaVulkanInstance::~QCocoaVulkanInstance()
{
}
void QCocoaVulkanInstance::createOrAdoptInstance()
{
initInstance(m_instance, QByteArrayList());
}
VkSurfaceKHR *QCocoaVulkanInstance::createSurface(QWindow *window)
{
QCocoaWindow *cocoaWindow = static_cast<QCocoaWindow *>(window->handle());
if (cocoaWindow->m_vulkanSurface)
destroySurface(cocoaWindow->m_vulkanSurface);
cocoaWindow->m_vulkanSurface = createSurface(cocoaWindow->m_view);
return &cocoaWindow->m_vulkanSurface;
}
VkSurfaceKHR QCocoaVulkanInstance::createSurface(NSView *view)
{
if (!m_createSurface) {
m_createSurface = reinterpret_cast<PFN_vkCreateMacOSSurfaceMVK>(
m_vkGetInstanceProcAddr(m_vkInst, "vkCreateMacOSSurfaceMVK"));
}
if (!m_createSurface) {
qWarning("Failed to find vkCreateMacOSSurfaceMVK");
return m_nullSurface;
}
if (!m_destroySurface) {
m_destroySurface = reinterpret_cast<PFN_vkDestroySurfaceKHR>(
m_vkGetInstanceProcAddr(m_vkInst, "vkDestroySurfaceKHR"));
}
if (!m_destroySurface) {
qWarning("Failed to find vkDestroySurfaceKHR");
return m_nullSurface;
}
VkMacOSSurfaceCreateInfoMVK surfaceInfo;
surfaceInfo.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK;
surfaceInfo.pNext = nullptr;
surfaceInfo.flags = 0;
surfaceInfo.pView = view;
VkSurfaceKHR surface = nullptr;
VkResult err = m_createSurface(m_vkInst, &surfaceInfo, nullptr, &surface);
if (err != VK_SUCCESS)
qWarning("Failed to create Vulkan surface: %d", err);
return surface;
}
void QCocoaVulkanInstance::destroySurface(VkSurfaceKHR surface)
{
if (m_destroySurface && surface)
m_destroySurface(m_vkInst, surface, nullptr);
}
QT_END_NAMESPACE

View File

@ -53,6 +53,10 @@
#include "qnswindow.h" #include "qnswindow.h"
#include "qt_mac_p.h" #include "qt_mac_p.h"
#if QT_CONFIG(vulkan)
#include <MoltenVK/mvk_vulkan.h>
#endif
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
#ifndef QT_NO_DEBUG_STREAM #ifndef QT_NO_DEBUG_STREAM
@ -281,6 +285,10 @@ public: // for QNSView
}; };
QHash<quintptr, BorderRange> m_contentBorderAreas; // identifer -> uppper/lower QHash<quintptr, BorderRange> m_contentBorderAreas; // identifer -> uppper/lower
QHash<quintptr, bool> m_enabledContentBorderAreas; // identifer -> enabled state (true/false) QHash<quintptr, bool> m_enabledContentBorderAreas; // identifer -> enabled state (true/false)
#if QT_CONFIG(vulkan)
VkSurfaceKHR m_vulkanSurface = nullptr;
#endif
}; };
#ifndef QT_NO_DEBUG_STREAM #ifndef QT_NO_DEBUG_STREAM

View File

@ -222,10 +222,16 @@ QCocoaWindow::~QCocoaWindow()
if (!isForeignWindow()) if (!isForeignWindow())
[[NSNotificationCenter defaultCenter] removeObserver:m_view]; [[NSNotificationCenter defaultCenter] removeObserver:m_view];
// While it is unlikely that this window will be in the popup stack if (QCocoaIntegration *cocoaIntegration = QCocoaIntegration::instance()) {
// during deletetion we clear any pointers here to make sure. // While it is unlikely that this window will be in the popup stack
if (QCocoaIntegration::instance()) { // during deletetion we clear any pointers here to make sure.
QCocoaIntegration::instance()->popupWindowStack()->removeAll(this); cocoaIntegration->popupWindowStack()->removeAll(this);
#if QT_CONFIG(vulkan)
auto vulcanInstance = cocoaIntegration->getCocoaVulkanInstance();
if (vulcanInstance)
vulcanInstance->destroySurface(m_vulkanSurface);
#endif
} }
[m_view release]; [m_view release];

View File

@ -141,13 +141,19 @@
// on and off is not a supported use-case, so this code is effectively // on and off is not a supported use-case, so this code is effectively
// returning a constant for the lifetime of our QSNSView, which means // returning a constant for the lifetime of our QSNSView, which means
// we don't care about emitting KVO signals for @"wantsLayer". // we don't care about emitting KVO signals for @"wantsLayer".
return qt_mac_resolveOption(false, m_platformWindow->window(), bool layerRequested = qt_mac_resolveOption(false, m_platformWindow->window(),
"_q_mac_wantsLayer", "QT_MAC_WANTS_LAYER"); "_q_mac_wantsLayer", "QT_MAC_WANTS_LAYER");
// Support Vulkan via MoltenVK, which requires a Metal layer
bool layerForVulkan = (m_platformWindow->window()->surfaceType() == QWindow::VulkanSurface);
return layerRequested || layerForVulkan;
} }
- (CALayer *)makeBackingLayer - (CALayer *)makeBackingLayer
{ {
bool makeMetalLayer = false; // TODO: Add/implement enabling API // Support Vulkan via MoltenVK, which requires a Metal layer
bool makeMetalLayer = (m_platformWindow->window()->surfaceType() == QWindow::VulkanSurface);
if (makeMetalLayer) { if (makeMetalLayer) {
// Check if Metal is supported. If it isn't then it's most likely // Check if Metal is supported. If it isn't then it's most likely
// too late at this point and the QWindow will be non-functional, // too late at this point and the QWindow will be non-functional,