aboutsummaryrefslogtreecommitdiffstats
path: root/src/quick/scenegraph/qsgrhisupport.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/quick/scenegraph/qsgrhisupport.cpp')
-rw-r--r--src/quick/scenegraph/qsgrhisupport.cpp567
1 files changed, 567 insertions, 0 deletions
diff --git a/src/quick/scenegraph/qsgrhisupport.cpp b/src/quick/scenegraph/qsgrhisupport.cpp
new file mode 100644
index 0000000000..2ca66f23d5
--- /dev/null
+++ b/src/quick/scenegraph/qsgrhisupport.cpp
@@ -0,0 +1,567 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQuick 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 "qsgrhisupport_p.h"
+#include "qsgdefaultrendercontext_p.h"
+#include <QtGui/qwindow.h>
+
+#if QT_CONFIG(vulkan)
+#include <QtGui/qvulkaninstance.h>
+#endif
+
+QT_BEGIN_NAMESPACE
+
+#if QT_CONFIG(vulkan)
+QVulkanInstance *s_vulkanInstance = nullptr;
+#endif
+
+QVulkanInstance *QSGRhiSupport::vulkanInstance()
+{
+#if QT_CONFIG(vulkan)
+ QSGRhiSupport *inst = QSGRhiSupport::instance();
+ if (!inst->isRhiEnabled() || inst->rhiBackend() != QRhi::Vulkan)
+ return nullptr;
+
+ if (!s_vulkanInstance) {
+ s_vulkanInstance = new QVulkanInstance;
+ if (inst->isDebugLayerRequested()) {
+#ifndef Q_OS_ANDROID
+ s_vulkanInstance->setLayers(QByteArrayList() << "VK_LAYER_LUNARG_standard_validation");
+#else
+ s_vulkanInstance->setLayers(QByteArrayList()
+ << "VK_LAYER_GOOGLE_threading"
+ << "VK_LAYER_LUNARG_parameter_validation"
+ << "VK_LAYER_LUNARG_object_tracker"
+ << "VK_LAYER_LUNARG_core_validation"
+ << "VK_LAYER_LUNARG_image"
+ << "VK_LAYER_LUNARG_swapchain"
+ << "VK_LAYER_GOOGLE_unique_objects");
+#endif
+ }
+ s_vulkanInstance->setExtensions(QByteArrayList()
+ << "VK_KHR_get_physical_device_properties2");
+ if (!s_vulkanInstance->create()) {
+ qWarning("Failed to create Vulkan instance");
+ delete s_vulkanInstance;
+ s_vulkanInstance = nullptr;
+ }
+ }
+ return s_vulkanInstance;
+#else
+ return nullptr;
+#endif
+}
+
+void QSGRhiSupport::cleanup()
+{
+#if QT_CONFIG(vulkan)
+ delete s_vulkanInstance;
+ s_vulkanInstance = nullptr;
+#endif
+}
+
+QSGRhiSupport::QSGRhiSupport()
+ : m_set(false),
+ m_enableRhi(false),
+ m_debugLayer(false),
+ m_profile(false),
+ m_shaderEffectDebug(false)
+{
+}
+
+void QSGRhiSupport::applySettings()
+{
+ m_set = true;
+
+ if (m_requested.valid) {
+ // explicit rhi backend request from C++ (e.g. via QQuickWindow)
+ m_enableRhi = m_requested.rhi;
+ switch (m_requested.api) {
+ case QSGRendererInterface::OpenGLRhi:
+ m_rhiBackend = QRhi::OpenGLES2;
+ break;
+ case QSGRendererInterface::Direct3D11Rhi:
+ m_rhiBackend = QRhi::D3D11;
+ break;
+ case QSGRendererInterface::VulkanRhi:
+ m_rhiBackend = QRhi::Vulkan;
+ break;
+ case QSGRendererInterface::MetalRhi:
+ m_rhiBackend = QRhi::Metal;
+ break;
+ case QSGRendererInterface::NullRhi:
+ m_rhiBackend = QRhi::Null;
+ break;
+ default:
+ Q_ASSERT_X(false, "QSGRhiSupport", "Internal error: unhandled GraphicsApi type");
+ break;
+ }
+ } else {
+ // check env.vars., fall back to platform-specific defaults when backend is not set
+ m_enableRhi = qEnvironmentVariableIntValue("QSG_RHI");
+ const QByteArray rhiBackend = qgetenv("QSG_RHI_BACKEND");
+ if (rhiBackend == QByteArrayLiteral("gl")
+ || rhiBackend == QByteArrayLiteral("gles2")
+ || rhiBackend == QByteArrayLiteral("opengl"))
+ {
+ m_rhiBackend = QRhi::OpenGLES2;
+ } else if (rhiBackend == QByteArrayLiteral("d3d11") || rhiBackend == QByteArrayLiteral("d3d")) {
+ m_rhiBackend = QRhi::D3D11;
+ } else if (rhiBackend == QByteArrayLiteral("vulkan")) {
+ m_rhiBackend = QRhi::Vulkan;
+ } else if (rhiBackend == QByteArrayLiteral("metal")) {
+ m_rhiBackend = QRhi::Metal;
+ } else if (rhiBackend == QByteArrayLiteral("null")) {
+ m_rhiBackend = QRhi::Null;
+ } else {
+#if defined(Q_OS_WIN)
+ m_rhiBackend = QRhi::D3D11;
+#elif defined(Q_OS_DARWIN)
+ m_rhiBackend = QRhi::Metal;
+#else
+ m_rhiBackend = QRhi::OpenGLES2;
+#endif
+ // Vulkan has to be requested explicitly
+ }
+ }
+
+ if (!m_enableRhi)
+ return;
+
+ // validation layers (Vulkan) or debug layer (D3D)
+ m_debugLayer = qEnvironmentVariableIntValue("QSG_RHI_DEBUG_LAYER");
+
+ // EnableProfiling + DebugMarkers
+ m_profile = qEnvironmentVariableIntValue("QSG_RHI_PROFILE");
+
+ m_shaderEffectDebug = qEnvironmentVariableIntValue("QSG_RHI_SHADEREFFECT_DEBUG");
+
+ const char *backendName = "unknown";
+ switch (m_rhiBackend) {
+ case QRhi::Null:
+ backendName = "Null";
+ break;
+ case QRhi::Vulkan:
+ backendName = "Vulkan";
+ break;
+ case QRhi::OpenGLES2:
+ backendName = "OpenGL";
+ break;
+ case QRhi::D3D11:
+ backendName = "D3D11";
+ break;
+ case QRhi::Metal:
+ backendName = "Metal";
+ break;
+ default:
+ break;
+ }
+ qDebug("Using QRhi with backend %s\n graphics API debug/validation layers: %d\n QRhi profiling and debug markers: %d",
+ backendName, m_debugLayer, m_profile);
+}
+
+QSGRhiSupport *QSGRhiSupport::staticInst()
+{
+ static QSGRhiSupport inst;
+ return &inst;
+}
+
+void QSGRhiSupport::configure(QSGRendererInterface::GraphicsApi api)
+{
+ Q_ASSERT(QSGRendererInterface::isApiRhiBased(api));
+ QSGRhiSupport *inst = staticInst();
+ if (inst->m_set) {
+ qWarning("QRhi is already configured, request ignored");
+ return;
+ }
+ inst->m_requested.valid = true;
+ inst->m_requested.api = api;
+ inst->m_requested.rhi = true;
+ inst->applySettings();
+}
+
+QSGRhiSupport *QSGRhiSupport::instance()
+{
+ QSGRhiSupport *inst = staticInst();
+ if (!inst->m_set)
+ inst->applySettings();
+ return inst;
+}
+
+QSGRendererInterface::GraphicsApi QSGRhiSupport::graphicsApi() const
+{
+ if (!m_enableRhi)
+ return QSGRendererInterface::OpenGL;
+
+ switch (m_rhiBackend) {
+ case QRhi::Null:
+ return QSGRendererInterface::NullRhi;
+ case QRhi::Vulkan:
+ return QSGRendererInterface::VulkanRhi;
+ case QRhi::OpenGLES2:
+ return QSGRendererInterface::OpenGLRhi;
+ case QRhi::D3D11:
+ return QSGRendererInterface::Direct3D11Rhi;
+ case QRhi::Metal:
+ return QSGRendererInterface::MetalRhi;
+ default:
+ return QSGRendererInterface::Unknown;
+ }
+}
+
+QSurface::SurfaceType QSGRhiSupport::windowSurfaceType() const
+{
+ if (!m_enableRhi)
+ return QSurface::OpenGLSurface;
+
+ switch (m_rhiBackend) {
+ case QRhi::Vulkan:
+ return QSurface::VulkanSurface;
+ case QRhi::OpenGLES2:
+ return QSurface::OpenGLSurface;
+ case QRhi::D3D11:
+ return QSurface::OpenGLSurface; // yup, OpenGLSurface
+ case QRhi::Metal:
+ return QSurface::MetalSurface;
+ default:
+ return QSurface::OpenGLSurface;
+ }
+}
+
+#if QT_CONFIG(vulkan)
+static const void *qsgrhi_vk_rifResource(QSGRendererInterface::Resource res, const QRhiNativeHandles *nat,
+ const QRhiNativeHandles *cbNat)
+{
+ const QRhiVulkanNativeHandles *vknat = static_cast<const QRhiVulkanNativeHandles *>(nat);
+ const QRhiVulkanCommandBufferNativeHandles *maybeVkCbNat =
+ static_cast<const QRhiVulkanCommandBufferNativeHandles *>(cbNat);
+
+ switch (res) {
+ case QSGRendererInterface::DeviceResource:
+ return &vknat->dev;
+ case QSGRendererInterface::CommandQueueResource:
+ return &vknat->gfxQueue;
+ case QSGRendererInterface::CommandListResource:
+ if (maybeVkCbNat)
+ return &maybeVkCbNat->commandBuffer;
+ else
+ return nullptr;
+ case QSGRendererInterface::PhysicalDeviceResource:
+ return &vknat->physDev;
+ default:
+ return nullptr;
+ }
+}
+#endif
+
+#if QT_CONFIG(opengl)
+static const void *qsgrhi_gl_rifResource(QSGRendererInterface::Resource res, const QRhiNativeHandles *nat)
+{
+ const QRhiGles2NativeHandles *glnat = static_cast<const QRhiGles2NativeHandles *>(nat);
+ switch (res) {
+ case QSGRendererInterface::OpenGLContextResource:
+ return glnat->context;
+ default:
+ return nullptr;
+ }
+}
+#endif
+
+#ifdef Q_OS_WIN
+static const void *qsgrhi_d3d11_rifResource(QSGRendererInterface::Resource res, const QRhiNativeHandles *nat)
+{
+ const QRhiD3D11NativeHandles *d3dnat = static_cast<const QRhiD3D11NativeHandles *>(nat);
+ switch (res) {
+ case QSGRendererInterface::DeviceResource:
+ return d3dnat->dev;
+ case QSGRendererInterface::DeviceContextResource:
+ return d3dnat->context;
+ default:
+ return nullptr;
+ }
+}
+#endif
+
+#ifdef Q_OS_DARWIN
+static const void *qsgrhi_mtl_rifResource(QSGRendererInterface::Resource res, const QRhiNativeHandles *nat,
+ const QRhiNativeHandles *cbNat)
+{
+ const QRhiMetalNativeHandles *mtlnat = static_cast<const QRhiMetalNativeHandles *>(nat);
+ const QRhiMetalCommandBufferNativeHandles *maybeMtlCbNat =
+ static_cast<const QRhiMetalCommandBufferNativeHandles *>(cbNat);
+
+ switch (res) {
+ case QSGRendererInterface::DeviceResource:
+ return mtlnat->dev;
+ case QSGRendererInterface::CommandQueueResource:
+ return mtlnat->cmdQueue;
+ case QSGRendererInterface::CommandListResource:
+ if (maybeMtlCbNat)
+ return maybeMtlCbNat->commandBuffer;
+ else
+ return nullptr;
+ case QSGRendererInterface::CommandEncoderResource:
+ if (maybeMtlCbNat)
+ return maybeMtlCbNat->encoder;
+ else
+ return nullptr;
+ default:
+ return nullptr;
+ }
+}
+#endif
+
+const void *QSGRhiSupport::rifResource(QSGRendererInterface::Resource res, const QSGDefaultRenderContext *rc)
+{
+ QRhi *rhi = rc->rhi();
+ if (res == QSGRendererInterface::RhiResource || !rhi)
+ return rhi;
+
+ const QRhiNativeHandles *nat = rhi->nativeHandles();
+ if (!nat)
+ return nullptr;
+
+ switch (m_rhiBackend) {
+#if QT_CONFIG(vulkan)
+ case QRhi::Vulkan:
+ {
+ QRhiCommandBuffer *cb = rc->currentFrameCommandBuffer();
+ return qsgrhi_vk_rifResource(res, nat, cb ? cb->nativeHandles() : nullptr);
+ }
+#endif
+#if QT_CONFIG(opengl)
+ case QRhi::OpenGLES2:
+ return qsgrhi_gl_rifResource(res, nat);
+#endif
+#ifdef Q_OS_WIN
+ case QRhi::D3D11:
+ return qsgrhi_d3d11_rifResource(res, nat);
+#endif
+#ifdef Q_OS_DARWIN
+ case QRhi::Metal:
+ {
+ QRhiCommandBuffer *cb = rc->currentFrameCommandBuffer();
+ return qsgrhi_mtl_rifResource(res, nat, cb ? cb->nativeHandles() : nullptr);
+ }
+#endif
+ default:
+ return nullptr;
+ }
+}
+
+int QSGRhiSupport::chooseSampleCountForWindowWithRhi(QWindow *window, QRhi *rhi)
+{
+ int msaaSampleCount = qMax(QSurfaceFormat::defaultFormat().samples(), window->requestedFormat().samples());
+ if (qEnvironmentVariableIsSet("QSG_SAMPLES"))
+ msaaSampleCount = qEnvironmentVariableIntValue("QSG_SAMPLES");
+ msaaSampleCount = qMax(1, msaaSampleCount);
+ if (msaaSampleCount > 1) {
+ const QVector<int> supportedSampleCounts = rhi->supportedSampleCounts();
+ if (!supportedSampleCounts.contains(msaaSampleCount)) {
+ int reducedSampleCount = 1;
+ for (int i = supportedSampleCounts.count() - 1; i >= 0; --i) {
+ if (supportedSampleCounts[i] <= msaaSampleCount) {
+ reducedSampleCount = supportedSampleCounts[i];
+ break;
+ }
+ }
+ qWarning() << "Requested MSAA sample count" << msaaSampleCount
+ << "but supported sample counts are" << supportedSampleCounts
+ << ", using sample count" << reducedSampleCount << "instead";
+ msaaSampleCount = reducedSampleCount;
+ }
+ }
+ return msaaSampleCount;
+}
+
+// must be called on the main thread
+QOffscreenSurface *QSGRhiSupport::maybeCreateOffscreenSurface(QWindow *window)
+{
+ QOffscreenSurface *offscreenSurface = nullptr;
+#if QT_CONFIG(opengl)
+ if (rhiBackend() == QRhi::OpenGLES2) {
+ const QSurfaceFormat format = window->requestedFormat();
+ offscreenSurface = QRhiGles2InitParams::newFallbackSurface(format);
+ }
+#else
+ Q_UNUSED(window);
+#endif
+ return offscreenSurface;
+}
+
+// must be called on the render thread
+QRhi *QSGRhiSupport::createRhi(QWindow *window, QOffscreenSurface *offscreenSurface)
+{
+ QRhi *rhi = nullptr;
+
+ QRhi::Flags flags = 0;
+ if (isProfilingRequested())
+ flags |= QRhi::EnableProfiling | QRhi::EnableDebugMarkers;
+
+ QRhi::Implementation backend = rhiBackend();
+ if (backend == QRhi::Null) {
+ QRhiNullInitParams rhiParams;
+ rhi = QRhi::create(backend, &rhiParams, flags);
+ }
+#if QT_CONFIG(opengl)
+ if (backend == QRhi::OpenGLES2) {
+ const QSurfaceFormat format = window->requestedFormat();
+ QRhiGles2InitParams rhiParams;
+ rhiParams.format = format;
+ rhiParams.fallbackSurface = offscreenSurface;
+ rhiParams.window = window;
+ rhi = QRhi::create(backend, &rhiParams, flags);
+ }
+#endif
+#if QT_CONFIG(vulkan)
+ if (backend == QRhi::Vulkan) {
+ QRhiVulkanInitParams rhiParams;
+ rhiParams.inst = window->vulkanInstance();
+ if (!rhiParams.inst)
+ qWarning("No QVulkanInstance set for QQuickWindow, this is wrong.");
+ rhiParams.window = window;
+ rhi = QRhi::create(backend, &rhiParams, flags);
+ }
+#endif
+#ifdef Q_OS_WIN
+ if (backend == QRhi::D3D11) {
+ QRhiD3D11InitParams rhiParams;
+ rhiParams.enableDebugLayer = isDebugLayerRequested();
+ rhi = QRhi::create(backend, &rhiParams, flags);
+ }
+#endif
+#ifdef Q_OS_DARWIN
+ if (backend == QRhi::Metal) {
+ QRhiMetalInitParams rhiParams;
+ rhi = QRhi::create(backend, &rhiParams, flags);
+ }
+#endif
+
+ if (!rhi)
+ qWarning("Failed to create RHI (backend %d)", backend);
+
+ return rhi;
+}
+
+QImage QSGRhiSupport::grabAndBlockInCurrentFrame(QRhi *rhi, QRhiSwapChain *swapchain)
+{
+ Q_ASSERT(rhi->isRecordingFrame());
+
+ QRhiReadbackResult result;
+ QRhiReadbackDescription readbackDesc; // read from swapchain backbuffer
+ QRhiResourceUpdateBatch *resourceUpdates = rhi->nextResourceUpdateBatch();
+ resourceUpdates->readBackTexture(readbackDesc, &result);
+
+ swapchain->currentFrameCommandBuffer()->resourceUpdate(resourceUpdates);
+ rhi->finish(); // make sure the readback has finished, stall the pipeline if needed
+
+ // May be RGBA or BGRA. Plus premultiplied alpha.
+ QImage::Format imageFormat;
+ if (result.format == QRhiTexture::BGRA8) {
+#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
+ imageFormat = QImage::Format_ARGB32_Premultiplied;
+#else
+ imageFormat = QImage::Format_RGBA8888_Premultiplied;
+ // ### and should swap too
+#endif
+ } else {
+ imageFormat = QImage::Format_RGBA8888_Premultiplied;
+ }
+
+ const uchar *p = reinterpret_cast<const uchar *>(result.data.constData());
+ const QImage img(p, result.pixelSize.width(), result.pixelSize.height(), imageFormat);
+
+ if (rhi->isYUpInFramebuffer())
+ return img.mirrored();
+
+ return img.copy();
+}
+
+QSGRhiProfileConnection *QSGRhiProfileConnection::instance()
+{
+ static QSGRhiProfileConnection inst;
+ return &inst;
+}
+
+void QSGRhiProfileConnection::initialize(QRhi *rhi)
+{
+#ifdef RHI_REMOTE_PROFILER
+ const QString profHost = qEnvironmentVariable("QSG_RHI_PROFILE_HOST");
+ if (!profHost.isEmpty()) {
+ int profPort = qEnvironmentVariableIntValue("QSG_RHI_PROFILE_PORT");
+ if (!profPort)
+ profPort = 30667;
+ qDebug("Sending RHI profiling output to %s:%d", qPrintable(profHost), profPort);
+ m_profConn.reset(new QTcpSocket);
+ QObject::connect(m_profConn.data(), QOverload<QAbstractSocket::SocketError>::of(&QAbstractSocket::error), m_profConn.data(),
+ [this](QAbstractSocket::SocketError socketError) { qDebug(" RHI profiler error: %d (%s)",
+ socketError, qPrintable(m_profConn->errorString())); });
+ m_profConn->connectToHost(profHost, profPort);
+ m_profConn->waitForConnected(); // blocking wait because we want to send stuff already from the init below
+ rhi->profiler()->setDevice(m_profConn.data());
+ m_lastMemStatWrite.start();
+ }
+#else
+ Q_UNUSED(rhi);
+#endif
+}
+
+void QSGRhiProfileConnection::cleanup()
+{
+#ifdef RHI_REMOTE_PROFILER
+ m_profConn.reset();
+#endif
+}
+
+void QSGRhiProfileConnection::send(QRhi *rhi)
+{
+#ifdef RHI_REMOTE_PROFILER
+ if (m_profConn) {
+ // do this every 5 sec at most
+ if (m_lastMemStatWrite.elapsed() >= 5000) {
+ rhi->profiler()->addVMemAllocatorStats();
+ m_lastMemStatWrite.restart();
+ }
+ }
+#else
+ Q_UNUSED(rhi);
+#endif
+}
+
+QT_END_NAMESPACE