Revision control
Copy as Markdown
// 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
// Qt-Security score:significant reason:default
#include "qsgrhisupport_p.h"
#include "qsgcontext_p.h"
#include "qsgdefaultrendercontext_p.h"
#include <QtQuick/private/qquickitem_p.h>
#include <QtQuick/private/qquickwindow_p.h>
#include <QtGui/qwindow.h>
#include <QtGui/qpa/qplatformintegration.h>
#include <QtGui/private/qguiapplication_p.h>
#if QT_CONFIG(vulkan)
#include <QtGui/private/qvulkandefaultinstance_p.h>
#endif
#include <QOperatingSystemVersion>
#include <QLockFile>
#include <QSaveFile>
#include <QStandardPaths>
#include <QDir>
#include <QFileInfo>
#include <QSysInfo>
#include <QOffscreenSurface>
#ifdef Q_OS_WIN
#include <dxgiformat.h>
#endif
#include <memory>
QT_BEGIN_NAMESPACE
QSGRhiSupport::QSGRhiSupport()
{
}
void QSGRhiSupport::applySettings()
{
// Multiple calls to this function are perfectly possible!
// Just store that it was called at least once.
m_settingsApplied = true;
// This is also done when creating the renderloop but we may be before that
// in case we get here due to a setGraphicsApi() -> configure() early
// on in main(). Avoid losing info logs since troubleshooting gets
// confusing otherwise.
QSGRhiSupport::checkEnvQSgInfo();
if (m_requested.valid) {
// explicit rhi backend request from C++ (e.g. via QQuickWindow)
switch (m_requested.api) {
case QSGRendererInterface::OpenGL:
m_rhiBackend = QRhi::OpenGLES2;
break;
case QSGRendererInterface::Direct3D11:
m_rhiBackend = QRhi::D3D11;
break;
case QSGRendererInterface::Direct3D12:
m_rhiBackend = QRhi::D3D12;
break;
case QSGRendererInterface::Vulkan:
m_rhiBackend = QRhi::Vulkan;
break;
case QSGRendererInterface::Metal:
m_rhiBackend = QRhi::Metal;
break;
case QSGRendererInterface::Null:
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
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("d3d12")) {
m_rhiBackend = QRhi::D3D12;
} 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 (!rhiBackend.isEmpty()) {
qWarning("Unknown key \"%s\" for QSG_RHI_BACKEND, falling back to default backend.",
rhiBackend.constData());
}
#if defined(Q_OS_WIN)
m_rhiBackend = QRhi::D3D11;
#elif QT_CONFIG(metal)
m_rhiBackend = QRhi::Metal;
#elif QT_CONFIG(opengl)
m_rhiBackend = QRhi::OpenGLES2;
#else
m_rhiBackend = QRhi::Vulkan;
#endif
// Now that we established our initial choice, we may want to opt
// for another backend under certain special circumstances.
adjustToPlatformQuirks();
}
}
// 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).
}
void QSGRhiSupport::adjustToPlatformQuirks()
{
#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;
if (!QRhi::probe(m_rhiBackend, &rhiParams)) {
qCDebug(QSG_LOG_INFO, "Metal does not seem to be supported. Falling back to OpenGL.");
m_rhiBackend = QRhi::OpenGLES2;
auto *platformIntegration = QGuiApplicationPrivate::platformIntegration();
if (platformIntegration
&& !platformIntegration->hasCapability(QPlatformIntegration::OpenGL)) {
qCWarning(QSG_LOG_INFO, "OpenGL not available. Falling back to Null backend.");
m_rhiBackend = QRhi::Null;
}
}
}
#endif
}
void QSGRhiSupport::checkEnvQSgInfo()
{
// For compatibility with 5.3 and earlier's QSG_INFO environment variables
if (qEnvironmentVariableIsSet("QSG_INFO"))
const_cast<QLoggingCategory &>(QSG_LOG_INFO()).setEnabled(QtDebugMsg, true);
}
QRhiRenderBuffer::Flags QSGRhiSupport::depthStencilBufferFlags()
{
static const bool noTransientDepthBuffer = qEnvironmentVariableIntValue("QSG_NO_TRANSIENT_DEPTH_BUFFER");
return noTransientDepthBuffer ? QRhiRenderBuffer::NoTransientBacking : QRhiRenderBuffer::Flags();
}
#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
m_requested.valid = false;
applySettings();
} else {
Q_ASSERT(QSGRendererInterface::isApiRhiBased(api));
m_requested.valid = true;
m_requested.api = api;
applySettings();
}
}
QSGRhiSupport *QSGRhiSupport::instance_internal()
{
static QSGRhiSupport inst;
return &inst;
}
QSGRhiSupport *QSGRhiSupport::instance()
{
QSGRhiSupport *inst = instance_internal();
if (!inst->m_settingsApplied)
inst->applySettings();
return inst;
}
QString QSGRhiSupport::rhiBackendName() const
{
return QString::fromUtf8(QRhi::backendName(m_rhiBackend));
}
QSGRendererInterface::GraphicsApi QSGRhiSupport::graphicsApi() const
{
switch (m_rhiBackend) {
case QRhi::Null:
return QSGRendererInterface::Null;
case QRhi::Vulkan:
return QSGRendererInterface::Vulkan;
case QRhi::OpenGLES2:
return QSGRendererInterface::OpenGL;
case QRhi::D3D11:
return QSGRendererInterface::Direct3D11;
case QRhi::D3D12:
return QSGRendererInterface::Direct3D12;
case QRhi::Metal:
return QSGRendererInterface::Metal;
default:
return QSGRendererInterface::Unknown;
}
}
QSurface::SurfaceType QSGRhiSupport::windowSurfaceType() const
{
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;
default:
return QSurface::OpenGLSurface;
}
}
#if QT_CONFIG(vulkan)
static const void *qsgrhi_vk_rifResource(QSGRendererInterface::Resource res,
const QRhiNativeHandles *nat,
const QRhiNativeHandles *cbNat,
const QRhiNativeHandles *rpNat)
{
const QRhiVulkanNativeHandles *vknat = static_cast<const QRhiVulkanNativeHandles *>(nat);
const QRhiVulkanCommandBufferNativeHandles *maybeVkCbNat =
static_cast<const QRhiVulkanCommandBufferNativeHandles *>(cbNat);
const QRhiVulkanRenderPassNativeHandles *maybeVkRpNat =
static_cast<const QRhiVulkanRenderPassNativeHandles *>(rpNat);
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;
case QSGRendererInterface::RenderPassResource:
if (maybeVkRpNat)
return &maybeVkRpNat->renderPass;
else
return nullptr;
case QSGRendererInterface::GraphicsQueueFamilyIndexResource:
return &vknat->gfxQueueFamilyIdx;
case QSGRendererInterface::GraphicsQueueIndexResource:
return &vknat->gfxQueueIdx;
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;
}
}
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 QT_CONFIG(metal)
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,
const QQuickWindow *w)
{
QRhi *rhi = rc->rhi();
if (!rhi)
return nullptr;
// Accessing the underlying QRhi* objects are essential both for Qt Quick
// 3D and advanced solutions, such as VR engine integrations.
switch (res) {
case QSGRendererInterface::RhiResource:
return rhi;
case QSGRendererInterface::RhiSwapchainResource:
return QQuickWindowPrivate::get(w)->swapchain;
case QSGRendererInterface::RhiRedirectCommandBuffer:
return QQuickWindowPrivate::get(w)->redirect.commandBuffer;
case QSGRendererInterface::RhiRedirectRenderTarget:
return QQuickWindowPrivate::get(w)->redirect.rt.rt.renderTarget;
default:
break;
}
const QRhiNativeHandles *nat = rhi->nativeHandles();
if (!nat)
return nullptr;
switch (m_rhiBackend) {
#if QT_CONFIG(vulkan)
case QRhi::Vulkan:
{
QRhiCommandBuffer *cb = rc->currentFrameCommandBuffer();
QRhiRenderPassDescriptor *rp = rc->currentFrameRenderPass();
return qsgrhi_vk_rifResource(res, nat,
cb ? cb->nativeHandles() : nullptr,
rp ? rp->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);
case QRhi::D3D12:
return qsgrhi_d3d12_rifResource(res, nat);
#endif
#if QT_CONFIG(metal)
case QRhi::Metal:
{
QRhiCommandBuffer *cb = rc->currentFrameCommandBuffer();
return qsgrhi_mtl_rifResource(res, nat, cb ? cb->nativeHandles() : nullptr);
}
#endif
default:
return nullptr;
}
}
int QSGRhiSupport::chooseSampleCount(int samples, QRhi *rhi)
{
int msaaSampleCount = samples;
if (qEnvironmentVariableIsSet("QSG_SAMPLES"))
msaaSampleCount = qEnvironmentVariableIntValue("QSG_SAMPLES");
msaaSampleCount = qMax(1, msaaSampleCount);
if (msaaSampleCount > 1) {
const QList<int> supportedSampleCounts = rhi->supportedSampleCounts();
if (!supportedSampleCounts.contains(msaaSampleCount)) {
int reducedSampleCount = 1;
for (int i = supportedSampleCounts.size() - 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;
}
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)
{
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;
}
void QSGRhiSupport::prepareWindowForRhi(QQuickWindow *window)
{
#if QT_CONFIG(vulkan)
if (rhiBackend() == QRhi::Vulkan) {
QQuickWindowPrivate *wd = QQuickWindowPrivate::get(window);
// QQuickWindows must get a QVulkanInstance automatically (it is
// created when the first window is constructed and is destroyed only
// on exit), unless the application decided to set its own. With
// QQuickRenderControl, no QVulkanInstance is created, because it must
// 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) {
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
}
static inline bool ensureWritableDir(const QString &name)
{
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);
}
#if !QT_CONFIG(temporaryfile)
static inline QString pipelineCacheLockFileName(const QString &name)
{
return name + QLatin1String(".lck");
}
#endif
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;
#if !QT_CONFIG(temporaryfile)
QLockFile lock(pipelineCacheLockFileName(pipelineCacheLoad));
if (!lock.lock()) {
qWarning("Could not create pipeline cache lock file '%s'",
qPrintable(lock.fileName()));
return;
}
#endif
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;
}
#if QT_CONFIG(temporaryfile)
QSaveFile f(pipelineCacheSave);
#else
QLockFile lock(pipelineCacheLockFileName(pipelineCacheSave));
if (!lock.lock()) {
qWarning("Could not create pipeline cache lock file '%s'",
qPrintable(lock.fileName()));
return;
}
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;
flags |= QRhi::SuppressSmokeTestWarnings;
if (debugMarkers)
flags |= QRhi::EnableDebugMarkers;
if (timestamps)
flags |= QRhi::EnableTimestamps;
if (preferSoftware)
flags |= QRhi::PreferSoftwareRenderer;
if (pipelineCacheSave)
flags |= QRhi::EnablePipelineCacheDataSave;
QRhiAdapter *rhiAdapter = customDevD->type == QQuickGraphicsDevicePrivate::Type::RhiAdapter ? customDevD->u.rhiAdapter : nullptr;
const QRhi::Implementation backend = rhiBackend();
if (backend == QRhi::Null) {
QRhiNullInitParams rhiParams;
rhi = QRhi::create(backend, &rhiParams, flags, nullptr, rhiAdapter);
}
#if QT_CONFIG(opengl)
if (backend == QRhi::OpenGLES2) {
const QSurfaceFormat format = window->requestedFormat();
QRhiGles2InitParams rhiParams;
rhiParams.format = format;
rhiParams.fallbackSurface = offscreenSurface;
rhiParams.window = window;
if (customDevD->type == QQuickGraphicsDevicePrivate::Type::OpenGLContext) {
QRhiGles2NativeHandles importDev;
importDev.context = customDevD->u.context;
qCDebug(QSG_LOG_INFO, "Using existing QOpenGLContext %p", importDev.context);
rhi = QRhi::create(backend, &rhiParams, flags, &importDev);
} else {
rhi = QRhi::create(backend, &rhiParams, flags, nullptr, rhiAdapter);
}
}
#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) {
if (debugLayer)
QVulkanDefaultInstance::setFlag(QVulkanDefaultInstance::EnableValidation, true);
QRhiVulkanInitParams rhiParams;
prepareWindowForRhi(window); // sets a vulkanInstance if not yet present
rhiParams.inst = window->vulkanInstance();
if (!rhiParams.inst)
qWarning("No QVulkanInstance set for QQuickWindow, this is wrong.");
if (window->handle()) // only used for vkGetPhysicalDeviceSurfaceSupportKHR and that implies having a valid native window
rhiParams.window = window;
rhiParams.deviceExtensions = wd->graphicsConfig.deviceExtensions();
if (customDevD->type == QQuickGraphicsDevicePrivate::Type::DeviceObjects) {
QRhiVulkanNativeHandles importDev;
importDev.physDev = reinterpret_cast<VkPhysicalDevice>(customDevD->u.deviceObjects.physicalDevice);
importDev.dev = reinterpret_cast<VkDevice>(customDevD->u.deviceObjects.device);
importDev.gfxQueueFamilyIdx = customDevD->u.deviceObjects.queueFamilyIndex;
importDev.gfxQueueIdx = customDevD->u.deviceObjects.queueIndex;
importDev.gfxQueueFlags = customDevD->u.deviceObjects.queueFlags;
qCDebug(QSG_LOG_INFO, "Using existing native Vulkan physical device %p device %p graphics queue family index %d",
importDev.physDev, importDev.dev, importDev.gfxQueueFamilyIdx);
rhi = QRhi::create(backend, &rhiParams, flags, &importDev);
} else if (customDevD->type == QQuickGraphicsDevicePrivate::Type::PhysicalDevice) {
QRhiVulkanNativeHandles importDev;
importDev.physDev = reinterpret_cast<VkPhysicalDevice>(customDevD->u.physicalDevice.physicalDevice);
qCDebug(QSG_LOG_INFO, "Using existing native Vulkan physical device %p", importDev.physDev);
rhi = QRhi::create(backend, &rhiParams, flags, &importDev);
} else {
rhi = QRhi::create(backend, &rhiParams, flags, nullptr, rhiAdapter);
}
}
#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) {
QRhiD3D11InitParams rhiParams;
rhiParams.enableDebugLayer = debugLayer;
if (customDevD->type == QQuickGraphicsDevicePrivate::Type::DeviceAndContext) {
QRhiD3D11NativeHandles importDev;
importDev.dev = customDevD->u.deviceAndContext.device;
importDev.context = customDevD->u.deviceAndContext.context;
qCDebug(QSG_LOG_INFO, "Using existing native D3D11 device %p and context %p",
importDev.dev, importDev.context);
rhi = QRhi::create(backend, &rhiParams, flags, &importDev);
} else if (customDevD->type == QQuickGraphicsDevicePrivate::Type::Adapter) {
QRhiD3D11NativeHandles importDev;
importDev.adapterLuidLow = customDevD->u.adapter.luidLow;
importDev.adapterLuidHigh = customDevD->u.adapter.luidHigh;
importDev.featureLevel = customDevD->u.adapter.featureLevel;
qCDebug(QSG_LOG_INFO, "Using D3D11 adapter LUID %u, %d and feature level %d",
importDev.adapterLuidLow, importDev.adapterLuidHigh, importDev.featureLevel);
rhi = QRhi::create(backend, &rhiParams, flags, &importDev);
} else {
rhi = QRhi::create(backend, &rhiParams, flags, nullptr, rhiAdapter);
if (!rhi && attemptReinitWithSwRastUponFail() && !flags.testFlag(QRhi::PreferSoftwareRenderer) && !rhiAdapter) {
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 {
QRhiAdapter *rhiAdapter = customDevD->type == QQuickGraphicsDevicePrivate::Type::RhiAdapter ? customDevD->u.rhiAdapter : nullptr;
rhi = QRhi::create(backend, &rhiParams, flags, nullptr, rhiAdapter);
if (!rhi && attemptReinitWithSwRastUponFail() && !flags.testFlag(QRhi::PreferSoftwareRenderer) && !rhiAdapter) {
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 QT_CONFIG(metal)
if (backend == QRhi::Metal) {
QRhiMetalInitParams rhiParams;
if (customDevD->type == QQuickGraphicsDevicePrivate::Type::DeviceAndCommandQueue) {
QRhiMetalNativeHandles importDev;
importDev.dev = (MTLDevice *) customDevD->u.deviceAndCommandQueue.device;
importDev.cmdQueue = (MTLCommandQueue *) customDevD->u.deviceAndCommandQueue.cmdQueue;
qCDebug(QSG_LOG_INFO, "Using existing native Metal device %p and command queue %p",
importDev.dev, importDev.cmdQueue);
rhi = QRhi::create(backend, &rhiParams, flags, &importDev);
} else {
rhi = QRhi::create(backend, &rhiParams, flags, nullptr, rhiAdapter);
}
}
#endif
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 { rhi, true };
}
void QSGRhiSupport::destroyRhi(QRhi *rhi, const QQuickGraphicsConfiguration &config)
{
if (!rhi)
return;
if (!rhi->isDeviceLost())
finalizePipelineCache(rhi, config);
delete rhi;
}
QImage QSGRhiSupport::grabAndBlockInCurrentFrame(QRhi *rhi, QRhiCommandBuffer *cb, QRhiTexture *src)
{
Q_ASSERT(rhi->isRecordingFrame());
QRhiReadbackResult result;
QRhiReadbackDescription readbackDesc(src); // null src == read from swapchain backbuffer
QRhiResourceUpdateBatch *resourceUpdates = rhi->nextResourceUpdateBatch();
resourceUpdates->readBackTexture(readbackDesc, &result);
cb->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.flipped();
return img.copy();
}
QImage QSGRhiSupport::grabOffscreen(QQuickWindow *window)
{
// Set up and then tear down the entire rendering infrastructure. This
// function is called on the gui/main thread - but that's alright because
// there is no onscreen rendering initialized at this point (so no render
// thread for instance).
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);
QScopedPointer<QOffscreenSurface> offscreenSurface(maybeCreateOffscreenSurface(window));
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,
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,
depthStencilBufferFlags()));
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();
}
wd->rhi = rhi;
QSGDefaultRenderContext::InitParams params;
params.rhi = rhi;
params.sampleCount = 1;
params.initialSurfacePixelSize = pixelSize;
params.maybeSurface = window;
wd->context->initialize(¶ms);
// There was no rendercontrol which means a custom render target
// should not be set either. Set our own, temporarily.
auto renderTarget = QQuickRenderTarget::fromRhiRenderTarget(rt.data());
// Pass on the scale factor. No need to use effectiveDevicePixelRatio(),
// because there is no rendercontrol anyway.
renderTarget.setDevicePixelRatio(window->devicePixelRatio());
window->setRenderTarget(renderTarget);
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());
wd->cleanupNodesOnShutdown();
wd->context->invalidate();
window->setRenderTarget(QQuickRenderTarget());
wd->rhi = nullptr;
return image;
}
#ifdef Q_OS_WEBOS
QImage QSGRhiSupport::grabOffscreenForProtectedContent(QQuickWindow *window)
{
// 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,
depthStencilBufferFlags()));
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 QSGRhiSupport::applySwapChainFormat(QRhiSwapChain *scWithWindowSet, QQuickWindow *window)
{
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);
}
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();
}
}
QRhiTexture::Format QSGRhiSupport::toRhiTextureFormat(uint nativeFormat, QRhiTexture::Flags *flags) const
{
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)
}
bool QSGRhiSupport::attemptReinitWithSwRastUponFail() const
{
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