diff options
Diffstat (limited to 'src/quick/scenegraph/qsgrhisupport.cpp')
-rw-r--r-- | src/quick/scenegraph/qsgrhisupport.cpp | 1317 |
1 files changed, 1022 insertions, 295 deletions
diff --git a/src/quick/scenegraph/qsgrhisupport.cpp b/src/quick/scenegraph/qsgrhisupport.cpp index 88bbf77e40..45c183a5f8 100644 --- a/src/quick/scenegraph/qsgrhisupport.cpp +++ b/src/quick/scenegraph/qsgrhisupport.cpp @@ -1,116 +1,37 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ +// Copyright (C) 2019 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 "qsgrhisupport_p.h" #include "qsgcontext_p.h" -# include "qsgdefaultrendercontext_p.h" +#include "qsgdefaultrendercontext_p.h" #include <QtQuick/private/qquickitem_p.h> #include <QtQuick/private/qquickwindow_p.h> #include <QtGui/qwindow.h> + #if QT_CONFIG(vulkan) -#include <QtGui/qvulkaninstance.h> +#include <QtGui/private/qvulkandefaultinstance_p.h> #endif #include <QOperatingSystemVersion> +#include <QLockFile> +#include <QSaveFile> +#include <QStandardPaths> +#include <QDir> +#include <QFileInfo> +#include <QSysInfo> #include <QOffscreenSurface> -#include <QtQml/private/qqmlengine_p.h> - -QT_BEGIN_NAMESPACE - -#if QT_CONFIG(vulkan) -QVulkanInstance *s_vulkanInstance = nullptr; +#ifdef Q_OS_WIN +#include <dxgiformat.h> #endif -QVulkanInstance *QSGRhiSupport::defaultVulkanInstance() -{ -#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(QRhiVulkanInitParams::preferredInstanceExtensions()); - if (!s_vulkanInstance->create()) { - qWarning("Failed to create Vulkan instance"); - delete s_vulkanInstance; - s_vulkanInstance = nullptr; - } - } - return s_vulkanInstance; -#else - return nullptr; -#endif -} +#include <memory> -void QSGRhiSupport::cleanupDefaultVulkanInstance() -{ -#if QT_CONFIG(vulkan) - delete s_vulkanInstance; - s_vulkanInstance = nullptr; -#endif -} +QT_BEGIN_NAMESPACE QSGRhiSupport::QSGRhiSupport() - : m_settingsApplied(false), - m_enableRhi(false), - m_debugLayer(false), - m_profile(false), - m_shaderEffectDebug(false), - m_preferSoftwareRenderer(false) { } @@ -128,21 +49,23 @@ void QSGRhiSupport::applySettings() if (m_requested.valid) { // explicit rhi backend request from C++ (e.g. via QQuickWindow) - m_enableRhi = true; switch (m_requested.api) { - case QSGRendererInterface::OpenGLRhi: + case QSGRendererInterface::OpenGL: m_rhiBackend = QRhi::OpenGLES2; break; - case QSGRendererInterface::Direct3D11Rhi: + case QSGRendererInterface::Direct3D11: m_rhiBackend = QRhi::D3D11; break; - case QSGRendererInterface::VulkanRhi: + case QSGRendererInterface::Direct3D12: + m_rhiBackend = QRhi::D3D12; + break; + case QSGRendererInterface::Vulkan: m_rhiBackend = QRhi::Vulkan; break; - case QSGRendererInterface::MetalRhi: + case QSGRendererInterface::Metal: m_rhiBackend = QRhi::Metal; break; - case QSGRendererInterface::NullRhi: + case QSGRendererInterface::Null: m_rhiBackend = QRhi::Null; break; default: @@ -150,10 +73,6 @@ void QSGRhiSupport::applySettings() break; } } else { - - // There is no other way in Qt 6. The direct OpenGL rendering path of Qt 5 has been removed. - m_enableRhi = true; - // check env.vars., fall back to platform-specific defaults when backend is not set const QByteArray rhiBackend = qgetenv("QSG_RHI_BACKEND"); if (rhiBackend == QByteArrayLiteral("gl") @@ -163,6 +82,8 @@ void QSGRhiSupport::applySettings() m_rhiBackend = QRhi::OpenGLES2; } else if (rhiBackend == QByteArrayLiteral("d3d11") || rhiBackend == QByteArrayLiteral("d3d")) { m_rhiBackend = QRhi::D3D11; + } else if (rhiBackend == QByteArrayLiteral("d3d12")) { + m_rhiBackend = QRhi::D3D12; } else if (rhiBackend == QByteArrayLiteral("vulkan")) { m_rhiBackend = QRhi::Vulkan; } else if (rhiBackend == QByteArrayLiteral("metal")) { @@ -176,7 +97,7 @@ void QSGRhiSupport::applySettings() } #if defined(Q_OS_WIN) m_rhiBackend = QRhi::D3D11; -#elif defined(Q_OS_MACOS) || defined(Q_OS_IOS) +#elif QT_CONFIG(metal) m_rhiBackend = QRhi::Metal; #elif QT_CONFIG(opengl) m_rhiBackend = QRhi::OpenGLES2; @@ -186,78 +107,33 @@ void QSGRhiSupport::applySettings() // Now that we established our initial choice, we may want to opt // for another backend under certain special circumstances. - if (m_enableRhi) // guard because this may do actual graphics calls on some platforms - adjustToPlatformQuirks(); + adjustToPlatformQuirks(); } } - Q_ASSERT(m_enableRhi); // cannot be anything else in Qt 6 - // At this point the RHI backend is fixed, it cannot be changed once we // return from this function. This is because things like the QWindow // (QQuickWindow) may depend on the graphics API as well (surfaceType // f.ex.), and all that is based on what we report from here. So further // adjustments are not possible (or, at minimum, not safe and portable). - - // validation layers (Vulkan) or debug layer (D3D) - m_debugLayer = uint(qEnvironmentVariableIntValue("QSG_RHI_DEBUG_LAYER")); - - // EnableProfiling + DebugMarkers - m_profile = uint(qEnvironmentVariableIntValue("QSG_RHI_PROFILE")); - - // EnablePipelineCacheDataSave - m_pipelineCacheSave = qEnvironmentVariable("QSG_RHI_PIPELINE_CACHE_SAVE"); - - m_pipelineCacheLoad = qEnvironmentVariable("QSG_RHI_PIPELINE_CACHE_LOAD"); - - m_shaderEffectDebug = uint(qEnvironmentVariableIntValue("QSG_RHI_SHADEREFFECT_DEBUG")); - - m_preferSoftwareRenderer = uint(qEnvironmentVariableIntValue("QSG_RHI_PREFER_SOFTWARE_RENDERER")); - - m_killDeviceFrameCount = qEnvironmentVariableIntValue("QSG_RHI_SIMULATE_DEVICE_LOSS"); - if (m_killDeviceFrameCount > 0 && m_rhiBackend == QRhi::D3D11) - qDebug("Graphics device will be reset every %d frames", m_killDeviceFrameCount); - - const QString backendName = rhiBackendName(); - qCDebug(QSG_LOG_INFO, - "Using QRhi with backend %s\n" - " Graphics API debug/validation layers: %d\n" - " QRhi profiling and debug markers: %d\n" - " Shader/pipeline cache collection: %d", - qPrintable(backendName), m_debugLayer, m_profile, !m_pipelineCacheSave.isEmpty()); - if (m_preferSoftwareRenderer) - qCDebug(QSG_LOG_INFO, "Prioritizing software renderers"); } void QSGRhiSupport::adjustToPlatformQuirks() { -#if defined(Q_OS_MACOS) || defined(Q_OS_IOS) - - // ### For now just create a throwaway QRhi instance. This will be replaced - // by a more lightweight way, once a helper function is added gui/rhi. - +#if QT_CONFIG(metal) // A macOS VM may not have Metal support at all. We have to decide at this // point, it will be too late afterwards, and the only way is to see if // MTLCreateSystemDefaultDevice succeeds. if (m_rhiBackend == QRhi::Metal) { QRhiMetalInitParams rhiParams; - QRhi *tempRhi = QRhi::create(m_rhiBackend, &rhiParams, {}); - if (!tempRhi) { + if (!QRhi::probe(m_rhiBackend, &rhiParams)) { m_rhiBackend = QRhi::OpenGLES2; qCDebug(QSG_LOG_INFO, "Metal does not seem to be supported. Falling back to OpenGL."); - } else { - delete tempRhi; } } #endif } -QSGRhiSupport *QSGRhiSupport::staticInst() -{ - static QSGRhiSupport inst; - return &inst; -} - void QSGRhiSupport::checkEnvQSgInfo() { // For compatibility with 5.3 and earlier's QSG_INFO environment variables @@ -265,25 +141,510 @@ void QSGRhiSupport::checkEnvQSgInfo() const_cast<QLoggingCategory &>(QSG_LOG_INFO()).setEnabled(QtDebugMsg, true); } + +#if QT_CONFIG(opengl) +#ifndef GL_BGRA +#define GL_BGRA 0x80E1 +#endif + +#ifndef GL_R8 +#define GL_R8 0x8229 +#endif + +#ifndef GL_RG8 +#define GL_RG8 0x822B +#endif + +#ifndef GL_RG +#define GL_RG 0x8227 +#endif + +#ifndef GL_R16 +#define GL_R16 0x822A +#endif + +#ifndef GL_RG16 +#define GL_RG16 0x822C +#endif + +#ifndef GL_RED +#define GL_RED 0x1903 +#endif + +#ifndef GL_RGBA8 +#define GL_RGBA8 0x8058 +#endif + +#ifndef GL_RGBA32F +#define GL_RGBA32F 0x8814 +#endif + +#ifndef GL_RGBA16F +#define GL_RGBA16F 0x881A +#endif + +#ifndef GL_R16F +#define GL_R16F 0x822D +#endif + +#ifndef GL_R32F +#define GL_R32F 0x822E +#endif + +#ifndef GL_DEPTH_COMPONENT16 +#define GL_DEPTH_COMPONENT16 0x81A5 +#endif + +#ifndef GL_DEPTH_COMPONENT24 +#define GL_DEPTH_COMPONENT24 0x81A6 +#endif + +#ifndef GL_DEPTH_COMPONENT32F +#define GL_DEPTH_COMPONENT32F 0x8CAC +#endif + +#ifndef GL_DEPTH24_STENCIL8 +#define GL_DEPTH24_STENCIL8 0x88F0 +#endif + +#ifndef GL_DEPTH_STENCIL +#define GL_DEPTH_STENCIL 0x84F9 +#endif + +#ifndef GL_RGB10_A2 +#define GL_RGB10_A2 0x8059 +#endif + +#ifndef GL_SRGB_ALPHA +#define GL_SRGB_ALPHA 0x8C42 +#endif + +#ifndef GL_SRGB8_ALPHA8 +#define GL_SRGB8_ALPHA8 0x8C43 +#endif + +QRhiTexture::Format QSGRhiSupport::toRhiTextureFormatFromGL(uint format, QRhiTexture::Flags *flags) +{ + bool sRGB = false; + auto rhiFormat = QRhiTexture::UnknownFormat; + switch (format) { + case GL_SRGB_ALPHA: + case GL_SRGB8_ALPHA8: + sRGB = true; + Q_FALLTHROUGH(); + case GL_RGBA: + case GL_RGBA8: + case 0: + rhiFormat = QRhiTexture::RGBA8; + break; + case GL_BGRA: + rhiFormat = QRhiTexture::BGRA8; + break; + case GL_R16: + rhiFormat = QRhiTexture::R16; + break; + case GL_RG16: + rhiFormat = QRhiTexture::RG16; + break; + case GL_RED: + Q_FALLTHROUGH(); + case GL_R8: + rhiFormat = QRhiTexture::R8; + break; + case GL_RG: + Q_FALLTHROUGH(); + case GL_RG8: + rhiFormat = QRhiTexture::RG8; + break; + case GL_ALPHA: + rhiFormat = QRhiTexture::RED_OR_ALPHA8; + break; + case GL_RGBA16F: + rhiFormat = QRhiTexture::RGBA16F; + break; + case GL_RGBA32F: + rhiFormat = QRhiTexture::RGBA32F; + break; + case GL_R16F: + rhiFormat = QRhiTexture::R16F; + break; + case GL_R32F: + rhiFormat = QRhiTexture::R32F; + break; + case GL_RGB10_A2: + rhiFormat = QRhiTexture::RGB10A2; + break; + case GL_DEPTH_COMPONENT: + Q_FALLTHROUGH(); + case GL_DEPTH_COMPONENT16: + rhiFormat = QRhiTexture::D16; + break; + case GL_DEPTH_COMPONENT24: + rhiFormat = QRhiTexture::D24; + break; + case GL_DEPTH_STENCIL: + Q_FALLTHROUGH(); + case GL_DEPTH24_STENCIL8: + rhiFormat = QRhiTexture::D24S8; + break; + case GL_DEPTH_COMPONENT32F: + rhiFormat = QRhiTexture::D32F; + break; + default: + qWarning("GL format %d is not supported", format); + break; + } + if (sRGB) + (*flags) |=(QRhiTexture::sRGB); + return rhiFormat; +} +#endif + +#if QT_CONFIG(vulkan) +QRhiTexture::Format QSGRhiSupport::toRhiTextureFormatFromVulkan(uint format, QRhiTexture::Flags *flags) +{ + auto rhiFormat = QRhiTexture::UnknownFormat; + bool sRGB = false; + switch (format) { + case VK_FORMAT_R8G8B8A8_SRGB: + sRGB = true; + Q_FALLTHROUGH(); + case VK_FORMAT_R8G8B8A8_UNORM: + case VK_FORMAT_UNDEFINED: + rhiFormat = QRhiTexture::RGBA8; + break; + case VK_FORMAT_B8G8R8A8_SRGB: + sRGB = true; + Q_FALLTHROUGH(); + case VK_FORMAT_B8G8R8A8_UNORM: + rhiFormat = QRhiTexture::BGRA8; + break; + case VK_FORMAT_R8_SRGB: + sRGB = true; + Q_FALLTHROUGH(); + case VK_FORMAT_R8_UNORM: + rhiFormat = QRhiTexture::R8; + break; + case VK_FORMAT_R8G8_SRGB: + sRGB = true; + Q_FALLTHROUGH(); + case VK_FORMAT_R8G8_UNORM: + rhiFormat = QRhiTexture::RG8; + break; + case VK_FORMAT_R16_UNORM: + rhiFormat = QRhiTexture::R16; + break; + case VK_FORMAT_R16G16_UNORM: + rhiFormat = QRhiTexture::RG16; + break; + case VK_FORMAT_R16G16B16A16_SFLOAT: + rhiFormat = QRhiTexture::RGBA16F; + break; + case VK_FORMAT_R32G32B32A32_SFLOAT: + rhiFormat = QRhiTexture::RGBA32F; + break; + case VK_FORMAT_R16_SFLOAT: + rhiFormat = QRhiTexture::R16F; + break; + case VK_FORMAT_R32_SFLOAT: + rhiFormat = QRhiTexture::R32F; + break; + case VK_FORMAT_A2B10G10R10_UNORM_PACK32: // intentionally + Q_FALLTHROUGH(); + case VK_FORMAT_A2R10G10B10_UNORM_PACK32: + rhiFormat = QRhiTexture::RGB10A2; + break; + case VK_FORMAT_D16_UNORM: + rhiFormat = QRhiTexture::D16; + break; + case VK_FORMAT_X8_D24_UNORM_PACK32: + rhiFormat = QRhiTexture::D24; + break; + case VK_FORMAT_D24_UNORM_S8_UINT: + rhiFormat = QRhiTexture::D24S8; + break; + case VK_FORMAT_D32_SFLOAT: + rhiFormat = QRhiTexture::D32F; + break; + case VK_FORMAT_BC1_RGB_SRGB_BLOCK: + sRGB = true; + Q_FALLTHROUGH(); + case VK_FORMAT_BC1_RGB_UNORM_BLOCK: + rhiFormat = QRhiTexture::BC1; + break; + case VK_FORMAT_BC2_SRGB_BLOCK: + sRGB = true; + Q_FALLTHROUGH(); + case VK_FORMAT_BC2_UNORM_BLOCK: + rhiFormat = QRhiTexture::BC2; + break; + case VK_FORMAT_BC3_SRGB_BLOCK: + sRGB = true; + Q_FALLTHROUGH(); + case VK_FORMAT_BC3_UNORM_BLOCK: + rhiFormat = QRhiTexture::BC3; + break; + case VK_FORMAT_BC4_UNORM_BLOCK: + rhiFormat = QRhiTexture::BC4; + break; + case VK_FORMAT_BC5_UNORM_BLOCK: + rhiFormat = QRhiTexture::BC5; + break; + case VK_FORMAT_BC6H_UFLOAT_BLOCK: + rhiFormat = QRhiTexture::BC6H; + break; + case VK_FORMAT_BC7_SRGB_BLOCK: + sRGB = true; + Q_FALLTHROUGH(); + case VK_FORMAT_BC7_UNORM_BLOCK: + rhiFormat = QRhiTexture::BC7; + break; + case VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK: + sRGB = true; + Q_FALLTHROUGH(); + case VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK: + rhiFormat = QRhiTexture::ETC2_RGB8; + break; + case VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK: + sRGB = true; + Q_FALLTHROUGH(); + case VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK: + rhiFormat = QRhiTexture::ETC2_RGB8A1; + break; + case VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK: + sRGB = true; + Q_FALLTHROUGH(); + case VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK: + rhiFormat = QRhiTexture::ETC2_RGBA8; + break; + case VK_FORMAT_ASTC_4x4_SRGB_BLOCK: + sRGB = true; + Q_FALLTHROUGH(); + case VK_FORMAT_ASTC_4x4_UNORM_BLOCK: + rhiFormat = QRhiTexture::ASTC_4x4; + break; + case VK_FORMAT_ASTC_5x4_SRGB_BLOCK: + sRGB = true; + Q_FALLTHROUGH(); + case VK_FORMAT_ASTC_5x4_UNORM_BLOCK: + rhiFormat = QRhiTexture::ASTC_5x4; + break; + case VK_FORMAT_ASTC_5x5_SRGB_BLOCK: + sRGB = true; + Q_FALLTHROUGH(); + case VK_FORMAT_ASTC_5x5_UNORM_BLOCK: + rhiFormat = QRhiTexture::ASTC_5x5; + break; + case VK_FORMAT_ASTC_6x5_SRGB_BLOCK: + sRGB = true; + Q_FALLTHROUGH(); + case VK_FORMAT_ASTC_6x5_UNORM_BLOCK: + rhiFormat = QRhiTexture::ASTC_6x5; + break; + case VK_FORMAT_ASTC_6x6_SRGB_BLOCK: + sRGB = true; + Q_FALLTHROUGH(); + case VK_FORMAT_ASTC_6x6_UNORM_BLOCK: + rhiFormat = QRhiTexture::ASTC_6x6; + break; + case VK_FORMAT_ASTC_8x5_SRGB_BLOCK: + sRGB = true; + Q_FALLTHROUGH(); + case VK_FORMAT_ASTC_8x5_UNORM_BLOCK: + rhiFormat = QRhiTexture::ASTC_8x5; + break; + case VK_FORMAT_ASTC_8x6_SRGB_BLOCK: + sRGB = true; + Q_FALLTHROUGH(); + case VK_FORMAT_ASTC_8x6_UNORM_BLOCK: + rhiFormat = QRhiTexture::ASTC_8x6; + break; + case VK_FORMAT_ASTC_8x8_SRGB_BLOCK: + sRGB = true; + Q_FALLTHROUGH(); + case VK_FORMAT_ASTC_8x8_UNORM_BLOCK: + rhiFormat = QRhiTexture::ASTC_8x8; + break; + case VK_FORMAT_ASTC_10x5_SRGB_BLOCK: + sRGB = true; + Q_FALLTHROUGH(); + case VK_FORMAT_ASTC_10x5_UNORM_BLOCK: + rhiFormat = QRhiTexture::ASTC_10x5; + break; + case VK_FORMAT_ASTC_10x6_SRGB_BLOCK: + sRGB = true; + Q_FALLTHROUGH(); + case VK_FORMAT_ASTC_10x6_UNORM_BLOCK: + rhiFormat = QRhiTexture::ASTC_10x6; + break; + case VK_FORMAT_ASTC_10x8_SRGB_BLOCK: + sRGB = true; + Q_FALLTHROUGH(); + case VK_FORMAT_ASTC_10x8_UNORM_BLOCK: + rhiFormat = QRhiTexture::ASTC_10x8; + break; + case VK_FORMAT_ASTC_10x10_SRGB_BLOCK: + sRGB = true; + Q_FALLTHROUGH(); + case VK_FORMAT_ASTC_10x10_UNORM_BLOCK: + rhiFormat = QRhiTexture::ASTC_10x10; + break; + case VK_FORMAT_ASTC_12x10_SRGB_BLOCK: + sRGB = true; + Q_FALLTHROUGH(); + case VK_FORMAT_ASTC_12x10_UNORM_BLOCK: + rhiFormat = QRhiTexture::ASTC_12x10; + break; + case VK_FORMAT_ASTC_12x12_SRGB_BLOCK: + sRGB = true; + Q_FALLTHROUGH(); + case VK_FORMAT_ASTC_12x12_UNORM_BLOCK: + rhiFormat = QRhiTexture::ASTC_12x12; + break; + default: + qWarning("VkFormat %d is not supported", format); + break; + } + if (sRGB) + (*flags) |=(QRhiTexture::sRGB); + return rhiFormat; +} +#endif + +#ifdef Q_OS_WIN +QRhiTexture::Format QSGRhiSupport::toRhiTextureFormatFromDXGI(uint format, QRhiTexture::Flags *flags) +{ + auto rhiFormat = QRhiTexture::UnknownFormat; + bool sRGB = false; + switch (format) { + case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB: + sRGB = true; + Q_FALLTHROUGH(); + case DXGI_FORMAT_R8G8B8A8_UNORM: + case DXGI_FORMAT_UNKNOWN: + rhiFormat = QRhiTexture::RGBA8; + break; + case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB: + sRGB = true; + Q_FALLTHROUGH(); + case DXGI_FORMAT_B8G8R8A8_UNORM: + rhiFormat = QRhiTexture::BGRA8; + break; + case DXGI_FORMAT_R8_UNORM: + rhiFormat = QRhiTexture::R8; + break; + case DXGI_FORMAT_R8G8_UNORM: + rhiFormat = QRhiTexture::RG8; + break; + case DXGI_FORMAT_R16_UNORM: + rhiFormat = QRhiTexture::R16; + break; + case DXGI_FORMAT_R16G16_UNORM: + rhiFormat = QRhiTexture::RG16; + break; + case DXGI_FORMAT_R16G16B16A16_FLOAT: + rhiFormat = QRhiTexture::RGBA16F; + break; + case DXGI_FORMAT_R32G32B32A32_FLOAT: + rhiFormat = QRhiTexture::RGBA32F; + break; + case DXGI_FORMAT_R16_FLOAT: + rhiFormat = QRhiTexture::R16F; + break; + case DXGI_FORMAT_R32_FLOAT: + rhiFormat = QRhiTexture::R32F; + break; + case DXGI_FORMAT_R10G10B10A2_UNORM: + rhiFormat = QRhiTexture::RGB10A2; + break; + case DXGI_FORMAT_R16_TYPELESS: + rhiFormat = QRhiTexture::D16; + break; + case DXGI_FORMAT_R24_UNORM_X8_TYPELESS: + rhiFormat = QRhiTexture::D24; + break; + case DXGI_FORMAT_D24_UNORM_S8_UINT: + rhiFormat = QRhiTexture::D24S8; + break; + case DXGI_FORMAT_R32_TYPELESS: + rhiFormat = QRhiTexture::D32F; + break; + case DXGI_FORMAT_BC1_UNORM_SRGB: + sRGB = true; + Q_FALLTHROUGH(); + case DXGI_FORMAT_BC1_UNORM: + rhiFormat = QRhiTexture::BC1; + break; + case DXGI_FORMAT_BC2_UNORM_SRGB: + sRGB = true; + Q_FALLTHROUGH(); + case DXGI_FORMAT_BC2_UNORM: + rhiFormat = QRhiTexture::BC2; + break; + case DXGI_FORMAT_BC3_UNORM_SRGB: + sRGB = true; + Q_FALLTHROUGH(); + case DXGI_FORMAT_BC3_UNORM: + rhiFormat = QRhiTexture::BC3; + break; + case DXGI_FORMAT_BC4_UNORM: + rhiFormat = QRhiTexture::BC4; + break; + case DXGI_FORMAT_BC5_UNORM: + rhiFormat = QRhiTexture::BC5; + break; + case DXGI_FORMAT_BC6H_UF16: + rhiFormat = QRhiTexture::BC6H; + break; + case DXGI_FORMAT_BC7_UNORM_SRGB: + sRGB = true; + Q_FALLTHROUGH(); + case DXGI_FORMAT_BC7_UNORM: + rhiFormat = QRhiTexture::BC7; + break; + default: + qWarning("DXGI_FORMAT %d is not supported", format); + break; + } + if (sRGB) + (*flags) |=(QRhiTexture::sRGB); + return rhiFormat; +} +#endif + +#if QT_CONFIG(metal) +namespace QSGRhiSupportMac { + QRhiTexture::Format toRhiTextureFormatFromMetal(uint format, QRhiTexture::Flags *flags); +} +QRhiTexture::Format QSGRhiSupport::toRhiTextureFormatFromMetal(uint format, QRhiTexture::Flags *flags) +{ + return QSGRhiSupportMac::toRhiTextureFormatFromMetal(format, flags); +} +#endif + void QSGRhiSupport::configure(QSGRendererInterface::GraphicsApi api) { if (api == QSGRendererInterface::Unknown) { // behave as if nothing was explicitly requested - QSGRhiSupport *inst = staticInst(); - inst->m_requested.valid = false; - inst->applySettings(); + m_requested.valid = false; + applySettings(); } else { Q_ASSERT(QSGRendererInterface::isApiRhiBased(api)); - QSGRhiSupport *inst = staticInst(); - inst->m_requested.valid = true; - inst->m_requested.api = api; - inst->applySettings(); + m_requested.valid = true; + m_requested.api = api; + applySettings(); } } +QSGRhiSupport *QSGRhiSupport::instance_internal() +{ + static QSGRhiSupport inst; + return &inst; +} + QSGRhiSupport *QSGRhiSupport::instance() { - QSGRhiSupport *inst = staticInst(); + QSGRhiSupport *inst = instance_internal(); if (!inst->m_settingsApplied) inst->applySettings(); return inst; @@ -291,41 +652,24 @@ QSGRhiSupport *QSGRhiSupport::instance() QString QSGRhiSupport::rhiBackendName() const { - if (m_enableRhi) { - switch (m_rhiBackend) { - case QRhi::Null: - return QLatin1String("Null"); - case QRhi::Vulkan: - return QLatin1String("Vulkan"); - case QRhi::OpenGLES2: - return QLatin1String("OpenGL"); - case QRhi::D3D11: - return QLatin1String("D3D11"); - case QRhi::Metal: - return QLatin1String("Metal"); - default: - return QLatin1String("Unknown"); - } - } - return QLatin1String("Unknown (RHI not enabled)"); + return QString::fromUtf8(QRhi::backendName(m_rhiBackend)); } QSGRendererInterface::GraphicsApi QSGRhiSupport::graphicsApi() const { - if (!m_enableRhi) - return QSGRendererInterface::OpenGL; - switch (m_rhiBackend) { case QRhi::Null: - return QSGRendererInterface::NullRhi; + return QSGRendererInterface::Null; case QRhi::Vulkan: - return QSGRendererInterface::VulkanRhi; + return QSGRendererInterface::Vulkan; case QRhi::OpenGLES2: - return QSGRendererInterface::OpenGLRhi; + return QSGRendererInterface::OpenGL; case QRhi::D3D11: - return QSGRendererInterface::Direct3D11Rhi; + return QSGRendererInterface::Direct3D11; + case QRhi::D3D12: + return QSGRendererInterface::Direct3D12; case QRhi::Metal: - return QSGRendererInterface::MetalRhi; + return QSGRendererInterface::Metal; default: return QSGRendererInterface::Unknown; } @@ -333,15 +677,13 @@ QSGRendererInterface::GraphicsApi QSGRhiSupport::graphicsApi() const 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: + case QRhi::D3D12: return QSurface::Direct3DSurface; case QRhi::Metal: return QSurface::MetalSurface; @@ -379,6 +721,10 @@ static const void *qsgrhi_vk_rifResource(QSGRendererInterface::Resource res, return &maybeVkRpNat->renderPass; else return nullptr; + case QSGRendererInterface::GraphicsQueueFamilyIndexResource: + return &vknat->gfxQueueFamilyIdx; + case QSGRendererInterface::GraphicsQueueIndexResource: + return &vknat->gfxQueueIdx; default: return nullptr; } @@ -411,9 +757,22 @@ static const void *qsgrhi_d3d11_rifResource(QSGRendererInterface::Resource res, return nullptr; } } + +static const void *qsgrhi_d3d12_rifResource(QSGRendererInterface::Resource res, const QRhiNativeHandles *nat) +{ + const QRhiD3D12NativeHandles *d3dnat = static_cast<const QRhiD3D12NativeHandles *>(nat); + switch (res) { + case QSGRendererInterface::DeviceResource: + return d3dnat->dev; + case QSGRendererInterface::CommandQueueResource: + return d3dnat->commandQueue; + default: + return nullptr; + } +} #endif -#if defined(Q_OS_MACOS) || defined(Q_OS_IOS) +#if QT_CONFIG(metal) static const void *qsgrhi_mtl_rifResource(QSGRendererInterface::Resource res, const QRhiNativeHandles *nat, const QRhiNativeHandles *cbNat) { @@ -460,7 +819,7 @@ const void *QSGRhiSupport::rifResource(QSGRendererInterface::Resource res, case QSGRendererInterface::RhiRedirectCommandBuffer: return QQuickWindowPrivate::get(w)->redirect.commandBuffer; case QSGRendererInterface::RhiRedirectRenderTarget: - return QQuickWindowPrivate::get(w)->redirect.rt.renderTarget; + return QQuickWindowPrivate::get(w)->redirect.rt.rt.renderTarget; default: break; } @@ -487,8 +846,10 @@ const void *QSGRhiSupport::rifResource(QSGRendererInterface::Resource res, #ifdef Q_OS_WIN case QRhi::D3D11: return qsgrhi_d3d11_rifResource(res, nat); + case QRhi::D3D12: + return qsgrhi_d3d12_rifResource(res, nat); #endif -#if defined(Q_OS_MACOS) || defined(Q_OS_IOS) +#if QT_CONFIG(metal) case QRhi::Metal: { QRhiCommandBuffer *cb = rc->currentFrameCommandBuffer(); @@ -500,9 +861,9 @@ const void *QSGRhiSupport::rifResource(QSGRendererInterface::Resource res, } } -int QSGRhiSupport::chooseSampleCountForWindowWithRhi(QWindow *window, QRhi *rhi) +int QSGRhiSupport::chooseSampleCount(int samples, QRhi *rhi) { - int msaaSampleCount = qMax(QSurfaceFormat::defaultFormat().samples(), window->requestedFormat().samples()); + int msaaSampleCount = samples; if (qEnvironmentVariableIsSet("QSG_SAMPLES")) msaaSampleCount = qEnvironmentVariableIntValue("QSG_SAMPLES"); msaaSampleCount = qMax(1, msaaSampleCount); @@ -510,7 +871,7 @@ int QSGRhiSupport::chooseSampleCountForWindowWithRhi(QWindow *window, QRhi *rhi) const QVector<int> supportedSampleCounts = rhi->supportedSampleCounts(); if (!supportedSampleCounts.contains(msaaSampleCount)) { int reducedSampleCount = 1; - for (int i = supportedSampleCounts.count() - 1; i >= 0; --i) { + for (int i = supportedSampleCounts.size() - 1; i >= 0; --i) { if (supportedSampleCounts[i] <= msaaSampleCount) { reducedSampleCount = supportedSampleCounts[i]; break; @@ -525,6 +886,11 @@ int QSGRhiSupport::chooseSampleCountForWindowWithRhi(QWindow *window, QRhi *rhi) return msaaSampleCount; } +int QSGRhiSupport::chooseSampleCountForWindowWithRhi(QWindow *window, QRhi *rhi) +{ + return chooseSampleCount(qMax(QSurfaceFormat::defaultFormat().samples(), window->requestedFormat().samples()), rhi); +} + // must be called on the main thread QOffscreenSurface *QSGRhiSupport::maybeCreateOffscreenSurface(QWindow *window) { @@ -552,29 +918,233 @@ void QSGRhiSupport::prepareWindowForRhi(QQuickWindow *window) // always be under the application's control then (since the default // instance we could create here would not be configurable by the // application in any way, and that is often not acceptable). - if (!window->vulkanInstance() && !wd->renderControl) - window->setVulkanInstance(QSGRhiSupport::defaultVulkanInstance()); + if (!window->vulkanInstance() && !wd->renderControl) { + QVulkanInstance *vkinst = QVulkanDefaultInstance::instance(); + if (vkinst) + qCDebug(QSG_LOG_INFO) << "Got Vulkan instance from QVulkanDefaultInstance, requested api version was" << vkinst->apiVersion(); + else + qCDebug(QSG_LOG_INFO) << "No Vulkan instance from QVulkanDefaultInstance, expect problems"; + window->setVulkanInstance(vkinst); + } } #else Q_UNUSED(window); #endif } -// must be called on the render thread -QRhi *QSGRhiSupport::createRhi(QQuickWindow *window, QOffscreenSurface *offscreenSurface) +static inline bool ensureWritableDir(const QString &name) { -#if !QT_CONFIG(opengl) && !QT_CONFIG(vulkan) && !defined(Q_OS_WIN) && !defined(Q_OS_MACOS) && !defined(Q_OS_IOS) - Q_UNUSED(window); + QDir::root().mkpath(name); + return QFileInfo(name).isWritable(); +} + +static QString automaticPipelineCacheDir() +{ + static bool checked = false; + static QString currentCacheDir; + static bool cacheWritable = false; + + if (checked) + return cacheWritable ? currentCacheDir : QString(); + + checked = true; + + // Intentionally not using the global cache path (GenericCacheLocation) - + // we do not want forever growing pipeline cache files that contain + // everything from all Qt apps ever run (that would affect load times + // eventually, resource use, etc.). Stick to being application-specific. + + const QString cachePath = QStandardPaths::writableLocation(QStandardPaths::CacheLocation); + const QString subPath = QLatin1String("/qtpipelinecache-") + QSysInfo::buildAbi() + QLatin1Char('/'); + + if (!cachePath.isEmpty()) { + currentCacheDir = cachePath + subPath; + cacheWritable = ensureWritableDir(currentCacheDir); + } + + return cacheWritable ? currentCacheDir : QString(); +} + +static inline QString automaticPipelineCacheFileName(QRhi *rhi) +{ + const QString cacheDir = automaticPipelineCacheDir(); + if (!cacheDir.isEmpty()) + return cacheDir + QLatin1String("qqpc_") + QString::fromLatin1(rhi->backendName()).toLower(); + + return QString(); +} + +static inline bool isAutomaticPipelineCacheLoadSkippedForWindow(Qt::WindowFlags wflags) +{ + return wflags.testFlag(Qt::ToolTip) || wflags.testFlag(Qt::SplashScreen); +} + +static inline bool isAutomaticPipelineCacheSaveSkippedForWindow(Qt::WindowFlags wflags) +{ + // this catches Tool, ToolTip, SplashScreen as well + return wflags.testFlag(Qt::Dialog) || wflags.testFlag(Qt::Popup); +} + +static inline QString pipelineCacheLockFileName(const QString &name) +{ + return name + QLatin1String(".lck"); +} + +void QSGRhiSupport::preparePipelineCache(QRhi *rhi, QQuickWindow *window) +{ + QQuickWindowPrivate *wd = QQuickWindowPrivate::get(window); + + // the explicitly set filename always takes priority as per docs + QString pipelineCacheLoad = wd->graphicsConfig.pipelineCacheLoadFile(); + bool isAutomatic = false; + if (pipelineCacheLoad.isEmpty() && wd->graphicsConfig.isAutomaticPipelineCacheEnabled()) { + if (!isAutomaticPipelineCacheLoadSkippedForWindow(window->flags())) { + pipelineCacheLoad = automaticPipelineCacheFileName(rhi); + isAutomatic = true; + } + } + + if (pipelineCacheLoad.isEmpty()) + return; + + QLockFile lock(pipelineCacheLockFileName(pipelineCacheLoad)); + if (!lock.lock()) { + qWarning("Could not create pipeline cache lock file '%s'", + qPrintable(lock.fileName())); + return; + } + + QFile f(pipelineCacheLoad); + if (!f.open(QIODevice::ReadOnly)) { + if (!isAutomatic) { + qWarning("Could not open pipeline cache source file '%s'", + qPrintable(pipelineCacheLoad)); + } + return; + } + + const QByteArray buf = f.readAll(); + if (!buf.isEmpty()) { + qCDebug(QSG_LOG_INFO, "Attempting to seed pipeline cache for QRhi %p from '%s'", + rhi, qPrintable(pipelineCacheLoad)); + rhi->setPipelineCacheData(buf); + } +} + +void QSGRhiSupport::finalizePipelineCache(QRhi *rhi, const QQuickGraphicsConfiguration &config) +{ + // output the rhi statistics about pipelines, as promised by the documentation + qCDebug(QSG_LOG_INFO, "Total time spent on pipeline creation during the lifetime of the QRhi %p was %lld ms", + rhi, rhi->statistics().totalPipelineCreationTime); + + // the explicitly set filename always takes priority as per docs + QString pipelineCacheSave = config.pipelineCacheSaveFile(); + bool isAutomatic = false; + if (pipelineCacheSave.isEmpty() && config.isAutomaticPipelineCacheEnabled()) { + pipelineCacheSave = automaticPipelineCacheFileName(rhi); + isAutomatic = true; + } + + if (pipelineCacheSave.isEmpty()) + return; + + const QByteArray buf = rhi->pipelineCacheData(); + + // If empty, do nothing. This is exactly what will happen if the rhi was + // created without QRhi::EnablePipelineCacheDataSave set. + if (buf.isEmpty()) { + if (isAutomatic) { + // Attempt to remove the file. If it does not exist or this fails, + // that's fine. The goal is just to prevent warnings from + // setPipelineCacheData in future runs, e.g. if the Qt or driver + // version does not match _and_ we do not generate any data at run + // time, then not writing the file out also means the warning would + // appear again and again on every run. Prevent that. + QDir().remove(pipelineCacheSave); + } + return; + } + + QLockFile lock(pipelineCacheLockFileName(pipelineCacheSave)); + if (!lock.lock()) { + qWarning("Could not create pipeline cache lock file '%s'", + qPrintable(lock.fileName())); + return; + } + +#if QT_CONFIG(temporaryfile) + QSaveFile f(pipelineCacheSave); +#else + QFile f(pipelineCacheSave); +#endif + if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) { + if (!isAutomatic) { + const QString msg = f.errorString(); + qWarning("Could not open pipeline cache output file '%s': %s", + qPrintable(pipelineCacheSave), qPrintable(msg)); + } + return; + } + + qCDebug(QSG_LOG_INFO, "Writing pipeline cache contents (%d bytes) for QRhi %p to '%s'", + int(buf.size()), rhi, qPrintable(pipelineCacheSave)); + + if (f.write(buf) != buf.size() +#if QT_CONFIG(temporaryfile) + || !f.commit() #endif + ) + { + if (!isAutomatic) { + const QString msg = f.errorString(); + qWarning("Could not write pipeline cache: %s", qPrintable(msg)); + } + return; + } +} +// must be called on the render thread +QSGRhiSupport::RhiCreateResult QSGRhiSupport::createRhi(QQuickWindow *window, QSurface *offscreenSurface, bool forcePreferSwRenderer) +{ QRhi *rhi = nullptr; + QQuickWindowPrivate *wd = QQuickWindowPrivate::get(window); + const QQuickGraphicsDevicePrivate *customDevD = QQuickGraphicsDevicePrivate::get(&wd->customDeviceObjects); + if (customDevD->type == QQuickGraphicsDevicePrivate::Type::Rhi) { + rhi = customDevD->u.rhi; + if (rhi) { + preparePipelineCache(rhi, window); + return { rhi, false }; + } + } + + const bool debugLayer = wd->graphicsConfig.isDebugLayerEnabled(); + const bool debugMarkers = wd->graphicsConfig.isDebugMarkersEnabled(); + const bool timestamps = wd->graphicsConfig.timestampsEnabled(); + const bool preferSoftware = wd->graphicsConfig.prefersSoftwareDevice() || forcePreferSwRenderer; + const bool pipelineCacheSave = !wd->graphicsConfig.pipelineCacheSaveFile().isEmpty() + || (wd->graphicsConfig.isAutomaticPipelineCacheEnabled() + && !isAutomaticPipelineCacheSaveSkippedForWindow(window->flags())); + + const QString backendName = rhiBackendName(); + qCDebug(QSG_LOG_INFO, + "Creating QRhi with backend %s for window %p (wflags 0x%X)\n" + " Graphics API debug/validation layers: %d\n" + " Debug markers: %d\n" + " Timestamps: %d\n" + " Prefer software device: %d%s\n" + " Shader/pipeline cache collection: %d", + qPrintable(backendName), window, int(window->flags()), debugLayer, + debugMarkers, timestamps, preferSoftware, forcePreferSwRenderer ? " [FORCED]" : "", pipelineCacheSave); QRhi::Flags flags; - if (isProfilingRequested()) - flags |= QRhi::EnableProfiling | QRhi::EnableDebugMarkers; - if (isSoftwareRendererRequested()) + flags |= QRhi::SuppressSmokeTestWarnings; + if (debugMarkers) + flags |= QRhi::EnableDebugMarkers; + if (timestamps) + flags |= QRhi::EnableTimestamps; + if (preferSoftware) flags |= QRhi::PreferSoftwareRenderer; - if (!m_pipelineCacheSave.isEmpty()) + if (pipelineCacheSave) flags |= QRhi::EnablePipelineCacheDataSave; const QRhi::Implementation backend = rhiBackend(); @@ -584,8 +1154,6 @@ QRhi *QSGRhiSupport::createRhi(QQuickWindow *window, QOffscreenSurface *offscree } #if QT_CONFIG(opengl) if (backend == QRhi::OpenGLES2) { - QQuickWindowPrivate *wd = QQuickWindowPrivate::get(window); - const QQuickGraphicsDevicePrivate *customDevD = QQuickGraphicsDevicePrivate::get(&wd->customDeviceObjects); const QSurfaceFormat format = window->requestedFormat(); QRhiGles2InitParams rhiParams; rhiParams.format = format; @@ -602,11 +1170,13 @@ QRhi *QSGRhiSupport::createRhi(QQuickWindow *window, QOffscreenSurface *offscree } #else Q_UNUSED(offscreenSurface); + if (backend == QRhi::OpenGLES2) + qWarning("OpenGL was requested for Qt Quick, but this build of Qt has no OpenGL support."); #endif #if QT_CONFIG(vulkan) if (backend == QRhi::Vulkan) { - QQuickWindowPrivate *wd = QQuickWindowPrivate::get(window); - const QQuickGraphicsDevicePrivate *customDevD = QQuickGraphicsDevicePrivate::get(&wd->customDeviceObjects); + if (debugLayer) + QVulkanDefaultInstance::setFlag(QVulkanDefaultInstance::EnableValidation, true); QRhiVulkanInitParams rhiParams; prepareWindowForRhi(window); // sets a vulkanInstance if not yet present rhiParams.inst = window->vulkanInstance(); @@ -633,17 +1203,14 @@ QRhi *QSGRhiSupport::createRhi(QQuickWindow *window, QOffscreenSurface *offscree rhi = QRhi::create(backend, &rhiParams, flags); } } +#else + if (backend == QRhi::Vulkan) + qWarning("Vulkan was requested for Qt Quick, but this build of Qt has no Vulkan support."); #endif #ifdef Q_OS_WIN if (backend == QRhi::D3D11) { - QQuickWindowPrivate *wd = QQuickWindowPrivate::get(window); - const QQuickGraphicsDevicePrivate *customDevD = QQuickGraphicsDevicePrivate::get(&wd->customDeviceObjects); QRhiD3D11InitParams rhiParams; - rhiParams.enableDebugLayer = isDebugLayerRequested(); - if (m_killDeviceFrameCount > 0) { - rhiParams.framesUntilKillingDeviceViaTdr = m_killDeviceFrameCount; - rhiParams.repeatDeviceKill = true; - } + rhiParams.enableDebugLayer = debugLayer; if (customDevD->type == QQuickGraphicsDevicePrivate::Type::DeviceAndContext) { QRhiD3D11NativeHandles importDev; importDev.dev = customDevD->u.deviceAndContext.device; @@ -661,13 +1228,42 @@ QRhi *QSGRhiSupport::createRhi(QQuickWindow *window, QOffscreenSurface *offscree rhi = QRhi::create(backend, &rhiParams, flags, &importDev); } else { rhi = QRhi::create(backend, &rhiParams, flags); + if (!rhi && attemptReinitWithSwRastUponFail() && !flags.testFlag(QRhi::PreferSoftwareRenderer)) { + qCDebug(QSG_LOG_INFO, "Failed to create a D3D device with default settings; " + "attempting to get a software rasterizer backed device instead"); + flags |= QRhi::PreferSoftwareRenderer; + rhi = QRhi::create(backend, &rhiParams, flags); + } + } + } else if (backend == QRhi::D3D12) { + QRhiD3D12InitParams rhiParams; + rhiParams.enableDebugLayer = debugLayer; + if (customDevD->type == QQuickGraphicsDevicePrivate::Type::DeviceAndContext) { + QRhiD3D12NativeHandles importDev; + importDev.dev = customDevD->u.deviceAndContext.device; + qCDebug(QSG_LOG_INFO, "Using existing native D3D12 device %p", importDev.dev); + rhi = QRhi::create(backend, &rhiParams, flags, &importDev); + } else if (customDevD->type == QQuickGraphicsDevicePrivate::Type::Adapter) { + QRhiD3D12NativeHandles importDev; + importDev.adapterLuidLow = customDevD->u.adapter.luidLow; + importDev.adapterLuidHigh = customDevD->u.adapter.luidHigh; + importDev.minimumFeatureLevel = customDevD->u.adapter.featureLevel; + qCDebug(QSG_LOG_INFO, "Using D3D12 adapter LUID %u, %d and minimum feature level %d", + importDev.adapterLuidLow, importDev.adapterLuidHigh, importDev.minimumFeatureLevel); + rhi = QRhi::create(backend, &rhiParams, flags, &importDev); + } else { + rhi = QRhi::create(backend, &rhiParams, flags); + if (!rhi && attemptReinitWithSwRastUponFail() && !flags.testFlag(QRhi::PreferSoftwareRenderer)) { + qCDebug(QSG_LOG_INFO, "Failed to create a D3D device with default settings; " + "attempting to get a software rasterizer backed device instead"); + flags |= QRhi::PreferSoftwareRenderer; + rhi = QRhi::create(backend, &rhiParams, flags); + } } } #endif -#if defined(Q_OS_MACOS) || defined(Q_OS_IOS) +#if QT_CONFIG(metal) if (backend == QRhi::Metal) { - QQuickWindowPrivate *wd = QQuickWindowPrivate::get(window); - const QQuickGraphicsDevicePrivate *customDevD = QQuickGraphicsDevicePrivate::get(&wd->customDeviceObjects); QRhiMetalInitParams rhiParams; if (customDevD->type == QQuickGraphicsDevicePrivate::Type::DeviceAndCommandQueue) { QRhiMetalNativeHandles importDev; @@ -682,44 +1278,23 @@ QRhi *QSGRhiSupport::createRhi(QQuickWindow *window, QOffscreenSurface *offscree } #endif - if (!rhi) { + if (rhi) { + qCDebug(QSG_LOG_INFO, "Created QRhi %p for window %p", rhi, window); + preparePipelineCache(rhi, window); + } else { qWarning("Failed to create RHI (backend %d)", backend); - return nullptr; } - if (!m_pipelineCacheLoad.isEmpty()) { - QFile f(m_pipelineCacheLoad); - if (f.open(QIODevice::ReadOnly)) { - qCDebug(QSG_LOG_INFO, "Attempting to seed pipeline cache from '%s'", - qPrintable(m_pipelineCacheLoad)); - rhi->setPipelineCacheData(f.readAll()); - } else { - qWarning("Could not open pipeline cache source file '%s'", - qPrintable(m_pipelineCacheLoad)); - } - } - - return rhi; + return { rhi, true }; } -void QSGRhiSupport::destroyRhi(QRhi *rhi) +void QSGRhiSupport::destroyRhi(QRhi *rhi, const QQuickGraphicsConfiguration &config) { if (!rhi) return; - if (!rhi->isDeviceLost()) { - if (!m_pipelineCacheSave.isEmpty()) { - QFile f(m_pipelineCacheSave); - if (f.open(QIODevice::WriteOnly | QIODevice::Truncate)) { - qCDebug(QSG_LOG_INFO, "Writing pipeline cache contents to '%s'", - qPrintable(m_pipelineCacheSave)); - f.write(rhi->pipelineCacheData()); - } else { - qWarning("Could not open pipeline cache output file '%s'", - qPrintable(m_pipelineCacheSave)); - } - } - } + if (!rhi->isDeviceLost()) + finalizePipelineCache(rhi, config); delete rhi; } @@ -771,11 +1346,13 @@ QImage QSGRhiSupport::grabOffscreen(QQuickWindow *window) Q_ASSERT(!wd->renderControl); QScopedPointer<QOffscreenSurface> offscreenSurface(maybeCreateOffscreenSurface(window)); - QScopedPointer<QRhi> rhi(createRhi(window, offscreenSurface.data())); - if (!rhi) { + RhiCreateResult rhiResult = createRhi(window, offscreenSurface.data()); + if (!rhiResult.rhi) { qWarning("Failed to initialize QRhi for offscreen readback"); return QImage(); } + std::unique_ptr<QRhi> rhiOwner(rhiResult.rhi); + QRhi *rhi = rhiResult.own ? rhiOwner.get() : rhiOwner.release(); const QSize pixelSize = window->size() * window->devicePixelRatio(); QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, pixelSize, 1, @@ -799,10 +1376,10 @@ QImage QSGRhiSupport::grabOffscreen(QQuickWindow *window) return QImage(); } - wd->rhi = rhi.data(); + wd->rhi = rhi; QSGDefaultRenderContext::InitParams params; - params.rhi = rhi.data(); + params.rhi = rhi; params.sampleCount = 1; params.initialSurfacePixelSize = pixelSize; params.maybeSurface = window; @@ -821,10 +1398,10 @@ QImage QSGRhiSupport::grabOffscreen(QQuickWindow *window) wd->setCustomCommandBuffer(cb); wd->polishItems(); wd->syncSceneGraph(); - wd->renderSceneGraph(window->size()); + wd->renderSceneGraph(); wd->setCustomCommandBuffer(nullptr); - QImage image = grabAndBlockInCurrentFrame(rhi.data(), cb, texture.data()); + QImage image = grabAndBlockInCurrentFrame(rhi, cb, texture.data()); rhi->endOffscreenFrame(); image.setDevicePixelRatio(window->devicePixelRatio()); @@ -837,60 +1414,210 @@ QImage QSGRhiSupport::grabOffscreen(QQuickWindow *window) return image; } -QSGRhiProfileConnection *QSGRhiProfileConnection::instance() +#ifdef Q_OS_WEBOS +QImage QSGRhiSupport::grabOffscreenForProtectedContent(QQuickWindow *window) { - static QSGRhiProfileConnection inst; - return &inst; + // If a context is created for protected content, grabbing GPU + // resources are restricted. For the case, normal context + // and surface are needed to allow CPU access. + // So dummy offscreen window is used here + // This function is called in rendering thread. + + QScopedPointer<QQuickWindow> offscreenWindow; + QQuickWindowPrivate *wd = QQuickWindowPrivate::get(window); + // It is expected that window is not using QQuickRenderControl, i.e. it is + // a normal QQuickWindow that just happens to be not exposed. + Q_ASSERT(!wd->renderControl); + + // If context and surface are created for protected content, + // CPU can't read the frame resources. So normal context and surface are needed. + if (window->requestedFormat().testOption(QSurfaceFormat::ProtectedContent)) { + QSurfaceFormat surfaceFormat = window->requestedFormat(); + surfaceFormat.setOption(QSurfaceFormat::ProtectedContent, false); + offscreenWindow.reset(new QQuickWindow()); + offscreenWindow->setFormat(surfaceFormat); + } + + QScopedPointer<QOffscreenSurface> offscreenSurface(maybeCreateOffscreenSurface(window)); + RhiCreateResult rhiResult = createRhi(offscreenWindow.data() ? offscreenWindow.data() : window, offscreenSurface.data()); + if (!rhiResult.rhi) { + qWarning("Failed to initialize QRhi for offscreen readback"); + return QImage(); + } + QScopedPointer<QRhi> rhiOwner(rhiResult.rhi); + QRhi *rhi = rhiResult.own ? rhiOwner.data() : rhiOwner.take(); + + const QSize pixelSize = window->size() * window->devicePixelRatio(); + QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, pixelSize, 1, + QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource)); + if (!texture->create()) { + qWarning("Failed to build texture for offscreen readback"); + return QImage(); + } + QScopedPointer<QRhiRenderBuffer> depthStencil(rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, pixelSize, 1)); + if (!depthStencil->create()) { + qWarning("Failed to create depth/stencil buffer for offscreen readback"); + return QImage(); + } + QRhiTextureRenderTargetDescription rtDesc(texture.data()); + rtDesc.setDepthStencilBuffer(depthStencil.data()); + QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget(rtDesc)); + QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor()); + rt->setRenderPassDescriptor(rpDesc.data()); + if (!rt->create()) { + qWarning("Failed to build render target for offscreen readback"); + return QImage(); + } + + // Backup the original Rhi + QRhi *currentRhi = wd->rhi; + wd->rhi = rhi; + + QSGDefaultRenderContext::InitParams params; + params.rhi = rhi; + params.sampleCount = 1; + params.initialSurfacePixelSize = pixelSize; + params.maybeSurface = window; + wd->context->initialize(¶ms); + + // Backup the original RenderTarget + QQuickRenderTarget currentRenderTarget = window->renderTarget(); + // There was no rendercontrol which means a custom render target + // should not be set either. Set our own, temporarily. + window->setRenderTarget(QQuickRenderTarget::fromRhiRenderTarget(rt.data())); + + QRhiCommandBuffer *cb = nullptr; + if (rhi->beginOffscreenFrame(&cb) != QRhi::FrameOpSuccess) { + qWarning("Failed to start recording the frame for offscreen readback"); + return QImage(); + } + + wd->setCustomCommandBuffer(cb); + wd->polishItems(); + wd->syncSceneGraph(); + wd->renderSceneGraph(); + wd->setCustomCommandBuffer(nullptr); + + QImage image = grabAndBlockInCurrentFrame(rhi, cb, texture.data()); + rhi->endOffscreenFrame(); + + image.setDevicePixelRatio(window->devicePixelRatio()); + + // Called from gui/main thread on no onscreen rendering initialized + if (!currentRhi) { + wd->cleanupNodesOnShutdown(); + wd->context->invalidate(); + + window->setRenderTarget(QQuickRenderTarget()); + wd->rhi = nullptr; + } else { + // Called from rendering thread for protected content + // Restore to original Rhi, RenderTarget and Context + window->setRenderTarget(currentRenderTarget); + wd->rhi = currentRhi; + params.rhi = currentRhi; + wd->context->initialize(¶ms); + } + + return image; } +#endif -void QSGRhiProfileConnection::initialize(QRhi *rhi) +void QSGRhiSupport::applySwapChainFormat(QRhiSwapChain *scWithWindowSet, QQuickWindow *window) { -#ifdef RHI_REMOTE_PROFILER - const QString profHost = qEnvironmentVariable("QSG_RHI_PROFILE_HOST"); - if (!profHost.isEmpty()) { - if (!QQmlEnginePrivate::qml_debugging_enabled) { - qWarning("RHI profiling cannot be enabled without QML debugging, for security reasons. " - "Set CONFIG+=qml_debug in the application project."); - return; + Q_ASSERT(scWithWindowSet->window() == window); + + QRhiSwapChain::Format swapChainFormat = QRhiSwapChain::SDR; + + QByteArray hdrRequest = qgetenv("QSG_RHI_HDR"); + if (hdrRequest.isEmpty()) + hdrRequest = window->property("_qt_sg_hdr_format").toByteArray(); + + if (!hdrRequest.isEmpty()) { + hdrRequest = hdrRequest.toLower(); + if (hdrRequest == QByteArrayLiteral("scrgb") || hdrRequest == QByteArrayLiteral("extendedsrgblinear")) + swapChainFormat = QRhiSwapChain::HDRExtendedSrgbLinear; + else if (hdrRequest == QByteArrayLiteral("hdr10")) + swapChainFormat = QRhiSwapChain::HDR10; + else if (hdrRequest == QByteArrayLiteral("p3")) + swapChainFormat = QRhiSwapChain::HDRExtendedDisplayP3Linear; + } + + const char *fmtStr = "unknown"; + switch (swapChainFormat) { + case QRhiSwapChain::SDR: + fmtStr = "SDR"; + break; + case QRhiSwapChain::HDRExtendedSrgbLinear: + fmtStr = "scRGB"; + break; + case QRhiSwapChain::HDR10: + fmtStr = "HDR10"; + break; + case QRhiSwapChain::HDRExtendedDisplayP3Linear: + fmtStr = "Extended Linear Display P3"; + break; + default: + break; + } + + if (!scWithWindowSet->isFormatSupported(swapChainFormat)) { + if (swapChainFormat != QRhiSwapChain::SDR) { + qCDebug(QSG_LOG_INFO, "Requested a %s swapchain but it is reported to be unsupported with the current display(s). " + "In multi-screen configurations make sure the window is located on a HDR-enabled screen. " + "Request ignored, using SDR swapchain.", fmtStr); } - int profPort = qEnvironmentVariableIntValue("QSG_RHI_PROFILE_PORT"); - if (!profPort) - profPort = 30667; - qCDebug(QSG_LOG_INFO, "Sending RHI profiling output to %s:%d", qPrintable(profHost), profPort); - m_profConn.reset(new QTcpSocket); - QObject::connect(m_profConn.data(), &QAbstractSocket::errorOccurred, m_profConn.data(), - [this](QAbstractSocket::SocketError socketError) { qWarning(" 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(); + return; + } + + scWithWindowSet->setFormat(swapChainFormat); + + if (swapChainFormat != QRhiSwapChain::SDR) { + qCDebug(QSG_LOG_INFO, "Creating %s swapchain", fmtStr); + qCDebug(QSG_LOG_INFO) << "HDR output info:" << scWithWindowSet->hdrInfo(); } -#else - Q_UNUSED(rhi); -#endif } -void QSGRhiProfileConnection::cleanup() +QRhiTexture::Format QSGRhiSupport::toRhiTextureFormat(uint nativeFormat, QRhiTexture::Flags *flags) const { -#ifdef RHI_REMOTE_PROFILER - m_profConn.reset(); + switch (m_rhiBackend) { +#if QT_CONFIG(vulkan) + case QRhi::Vulkan: + return toRhiTextureFormatFromVulkan(nativeFormat, flags); #endif +#if QT_CONFIG(opengl) + case QRhi::OpenGLES2: + Q_UNUSED(flags); + return toRhiTextureFormatFromGL(nativeFormat, flags); +#endif +#ifdef Q_OS_WIN + case QRhi::D3D11: + case QRhi::D3D12: + return toRhiTextureFormatFromDXGI(nativeFormat, flags); +#endif +#if QT_CONFIG(metal) + case QRhi::Metal: + return toRhiTextureFormatFromMetal(nativeFormat, flags); +#endif + default: + return QRhiTexture::UnknownFormat; + } + Q_UNUSED(nativeFormat) + Q_UNUSED(flags) } -void QSGRhiProfileConnection::send(QRhi *rhi) +bool QSGRhiSupport::attemptReinitWithSwRastUponFail() const { -#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 + const QRhi::Implementation backend = rhiBackend(); + + // On Windows it makes sense to retry using a software adapter whenever + // device creation or swapchain creation fails, as WARP is usually available + // (built in to the OS) and is good quality. This helps a lot in particular + // when running in a VM that cripples proper 3D graphics. + if (backend == QRhi::D3D11 || backend == QRhi::D3D12) + return true; + + return false; } QT_END_NAMESPACE |