Revision control
++ // Copyright (C) 2025 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 "qthreadlocalrhi_p.h"
++
++ #include <QtCore/qcoreapplication.h>
++ #include <QtCore/qloggingcategory.h>
++ #include <QtCore/qthreadstorage.h>
++ #include <QtGui/private/qguiapplication_p.h>
++ #include <QtGui/qoffscreensurface.h>
++ #include <QtGui/qpa/qplatformintegration.h>
++
++ #if QT_CONFIG(vulkan)
++ # include <QtGui/qvulkaninstance.h>
++ #endif
++
++ #if defined(Q_OS_ANDROID)
++ # include <QtCore/qmetaobject.h>
++ #endif
++
++ QT_BEGIN_NAMESPACE
++
++ namespace {
++
++ Q_STATIC_LOGGING_CATEGORY(qLcThreadLocalRhi, "qt.multimedia.threadlocalrhi")
++
++ static thread_local QRhi::Implementation s_preferredBackend = QRhi::Null;
++
++ #if QT_CONFIG(opengl)
++
++ bool openGLCapsSupported(const QPlatformIntegration &qpa)
++ {
++ return qpa.hasCapability(QPlatformIntegration::OpenGL) &&
++ !QCoreApplication::testAttribute(Qt::AA_ForceRasterWidgets) &&
++ (QThread::isMainThread() ||
++ (qpa.hasCapability(QPlatformIntegration::ThreadedOpenGL) &&
++ qpa.hasCapability(QPlatformIntegration::OffscreenSurface)));
++ }
++ #endif
++
++ class ThreadLocalRhiHolder
++ {
++ public:
++ ~ThreadLocalRhiHolder() { resetRhi(); }
++
++ QRhi *ensureRhi([[maybe_unused]] QRhi::Implementation backend)
++ {
++ if (m_rhi || m_cpuOnly)
++ return m_rhi.get();
++
++ const QPlatformIntegration *qpa = QGuiApplicationPrivate::platformIntegration();
++
++ if (qpa && qpa->hasCapability(QPlatformIntegration::RhiBasedRendering)) {
++
++ #if QT_CONFIG(metal)
++ if (canUseRhiImpl(QRhi::Metal, backend)) {
++ QRhiMetalInitParams params;
++ m_rhi.reset(QRhi::create(QRhi::Metal, ¶ms));
++ }
++ #endif
++
++ #if defined(Q_OS_WIN)
++ if (!m_rhi && canUseRhiImpl(QRhi::D3D11, backend)) {
++ QRhiD3D11InitParams params;
++ m_rhi.reset(QRhi::create(QRhi::D3D11, ¶ms));
++ }
++
++ if (!m_rhi && canUseRhiImpl(QRhi::D3D12, backend)) {
++ QRhiD3D12InitParams params;
++ m_rhi.reset(QRhi::create(QRhi::D3D12, ¶ms));
++ }
++ #endif
++
++ #if QT_CONFIG(opengl)
++ if (!m_rhi && canUseRhiImpl(QRhi::OpenGLES2, backend)) {
++ if (openGLCapsSupported(*qpa)) {
++
++ m_fallbackSurface.reset(QRhiGles2InitParams::newFallbackSurface());
++ QRhiGles2InitParams params;
++ params.fallbackSurface = m_fallbackSurface.get();
++ m_rhi.reset(QRhi::create(QRhi::OpenGLES2, ¶ms));
++
++ # if defined(Q_OS_ANDROID)
++ // reset RHI state on application suspension, as this will be invalid after
++ // resuming
++ if (!m_appStateChangedConnection) {
++ if (!m_eventsReceiver)
++ m_eventsReceiver = std::make_unique<QObject>();
++
++ auto onStateChanged = [this](auto state) {
++ if (state == Qt::ApplicationSuspended)
++ resetRhi();
++ };
++
++ m_appStateChangedConnection =
++ QObject::connect(qApp, &QGuiApplication::applicationStateChanged,
++ m_eventsReceiver.get(), onStateChanged);
++ }
++ # endif
++ }
++ }
++ #endif
++
++ #if QT_CONFIG(vulkan)
++ if (!m_rhi && canUseRhiImpl(QRhi::Vulkan, backend)) {
++ auto instance = std::make_unique<QVulkanInstance>();
++ instance->setExtensions(QRhiVulkanInitParams::preferredInstanceExtensions());
++ if (instance->create()) {
++ QRhiVulkanInitParams params;
++ params.inst = instance.get();
++ m_rhi.reset(QRhi::create(QRhi::Vulkan, ¶ms));
++ } else {
++ qCDebug(qLcThreadLocalRhi) << "Failed to create QVulkanInstance";
++ }
++
++ if (m_rhi)
++ m_vulkanInstance = std::move(instance);
++ }
++ #endif
++ }
++
++ if (!m_rhi) {
++ m_cpuOnly = true;
++ qCWarning(qLcThreadLocalRhi)
++ << Q_FUNC_INFO << ": No RHI backend. Using CPU conversion.";
++ }
++
++ return m_rhi.get();
++ }
++
++ void resetRhi()
++ {
++ m_rhi.reset();
++ #if QT_CONFIG(opengl)
++ m_fallbackSurface.reset();
++ #endif
++ #if QT_CONFIG(vulkan)
++ m_vulkanInstance.reset();
++ #endif
++ m_cpuOnly = false;
++ }
++
++ bool canUseRhiImpl(const QRhi::Implementation implementation,
++ const QRhi::Implementation reference)
++ {
++ // First priority goes to reference backend
++ if (reference != QRhi::Null)
++ return implementation == reference;
++
++ // If no reference, but preference exists, compare to that
++ if (s_preferredBackend != QRhi::Null)
++ return implementation == s_preferredBackend;
++
++ // Can use (assuming platform and configuration allow)
++ return true;
++ }
++
++ private:
++ std::unique_ptr<QRhi> m_rhi;
++ #if QT_CONFIG(opengl)
++ std::unique_ptr<QOffscreenSurface> m_fallbackSurface;
++ #endif
++ #if QT_CONFIG(vulkan)
++ std::unique_ptr<QVulkanInstance> m_vulkanInstance;
++ #endif
++ bool m_cpuOnly = false;
++ #if defined(Q_OS_ANDROID)
++ std::unique_ptr<QObject> m_eventsReceiver;
++ // we keep and check QMetaObject::Connection because the sender, qApp,
++ // can be recreated and the connection invalidated.
++ QMetaObject::Connection m_appStateChangedConnection;
++ #endif
++ };
++
++ QThreadStorage<ThreadLocalRhiHolder> g_threadLocalRhiHolder;
++
++ }
++
++ QRhi *qEnsureThreadLocalRhi(QRhi::Implementation backend)
++ {
++ return g_threadLocalRhiHolder.localData().ensureRhi(backend);
++ }
++
++ void qSetPreferredThreadLocalRhiBackend(QRhi::Implementation backend)
++ {
++ if (s_preferredBackend != backend) {
++ s_preferredBackend = backend;
++ g_threadLocalRhiHolder.localData().resetRhi();
++ }
++ }
++
++ QT_END_NAMESPACE