Revision control

+ // Copyright (C) 2026 The Qt Company Ltd.
+ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+ #include <QtTest>
+
+ #include <QtGui/QSurfaceFormat>
+ #include <QtQuick3DRuntimeRender/private/qssgrendershadercache_p.h>
+
+ #ifdef QT_QUICK3D_HAS_RUNTIME_SHADERS
+ #include <rhi/qshaderbaker.h>
+ #endif
+
+ class tst_ShaderCache : public QObject
+ {
+ Q_OBJECT
+
+ private slots:
+ void glslTargetsForContext_data();
+ void glslTargetsForContext();
+ void persistentBakerTargets();
+ };
+
+ // "330, 460" or "300es, 320es". Comparing the rendered form rather than the
+ // QShaderVersion list keeps the ES flavour visible in the failure message --
+ // dropping it is exactly the kind of mistake this test is here to catch.
+ static QByteArray describe(const QList<QShaderVersion> &versions)
+ {
+ QByteArray result;
+ for (const QShaderVersion &version : versions) {
+ if (!result.isEmpty())
+ result += ", ";
+ result += QByteArray::number(version.version());
+ if (version.flags().testFlag(QShaderVersion::GlslEs))
+ result += "es";
+ }
+ return result;
+ }
+
+ void tst_ShaderCache::glslTargetsForContext_data()
+ {
+ QTest::addColumn<int>("majorVersion");
+ QTest::addColumn<int>("minorVersion");
+ QTest::addColumn<int>("profile");
+ QTest::addColumn<int>("renderableType");
+ QTest::addColumn<bool>("isGLESModule");
+ QTest::addColumn<QByteArray>("expected");
+
+ const int core = QSurfaceFormat::CoreProfile;
+ const int compat = QSurfaceFormat::CompatibilityProfile;
+ const int noProfile = QSurfaceFormat::NoProfile;
+ const int gl = QSurfaceFormat::OpenGL;
+ const int gles = QSurfaceFormat::OpenGLES;
+
+ // Core profile: the 330 floor plus the context's own version, and no
+ // duplicate 330 when the context is exactly 3.3.
+ QTest::newRow("core 3.3") << 3 << 3 << core << gl << false << QByteArray("330");
+ QTest::newRow("core 4.3") << 4 << 3 << core << gl << false << QByteArray("330, 430");
+ QTest::newRow("core 4.6") << 4 << 6 << core << gl << false << QByteArray("330, 460");
+
+ // Below 3.3, and any non-core profile, fall back to the fixed 140/130.
+ QTest::newRow("core 3.2") << 3 << 2 << core << gl << false << QByteArray("140");
+ QTest::newRow("compat 4.6") << 4 << 6 << compat << gl << false << QByteArray("140");
+ QTest::newRow("desktop 3.0") << 3 << 0 << noProfile << gl << false << QByteArray("130");
+
+ // GLES: same shape, floored at 300, and every version has to keep the es
+ // flavour or QRhiGles2 will not find the shader at all.
+ QTest::newRow("gles 2.0") << 2 << 0 << noProfile << gles << false << QByteArray("100es");
+ QTest::newRow("gles 3.0") << 3 << 0 << noProfile << gles << false << QByteArray("300es");
+ QTest::newRow("gles 3.1") << 3 << 1 << noProfile << gles << false << QByteArray("300es, 310es");
+ QTest::newRow("gles 3.2") << 3 << 2 << noProfile << gles << false << QByteArray("300es, 320es");
+
+ // A GLES build of Qt takes the same path even when the format does not say
+ // so itself.
+ QTest::newRow("gles module") << 3 << 1 << noProfile << gl << true << QByteArray("300es, 310es");
+ }
+
+ void tst_ShaderCache::glslTargetsForContext()
+ {
+ QFETCH(int, majorVersion);
+ QFETCH(int, minorVersion);
+ QFETCH(int, profile);
+ QFETCH(int, renderableType);
+ QFETCH(bool, isGLESModule);
+ QFETCH(QByteArray, expected);
+
+ QSurfaceFormat format;
+ format.setVersion(majorVersion, minorVersion);
+ format.setProfile(QSurfaceFormat::OpenGLContextProfile(profile));
+ format.setRenderableType(QSurfaceFormat::RenderableType(renderableType));
+
+ const QByteArray actual = describe(QSSGShaderCache::glslTargetsForContext(format, isGLESModule));
+ QCOMPARE(actual, expected);
+ }
+
+ void tst_ShaderCache::persistentBakerTargets()
+ {
+ #ifndef QT_QUICK3D_HAS_RUNTIME_SHADERS
+ QSKIP("Built without QtShaderTools, nothing bakes");
+ #else
+ QShaderBaker baker;
+ baker.setSourceString("#version 440\n"
+ "void main() { gl_Position = vec4(0.0, 0.0, 0.0, 1.0); }\n",
+ QShader::VertexStage,
+ QLatin1String("tst_shadercache.vert"));
+ QSSGShaderCache::initBakerForPersistentUse(&baker, nullptr);
+
+ const QShader shader = baker.bake();
+ QVERIFY2(shader.isValid(), qPrintable(baker.errorMessage()));
+
+ // The persistent cache file outlives the run that wrote it and may be
+ // carried to another backend or platform, so every target has to be there.
+ // A shader this trivial translates everywhere, meaning anything missing is
+ // a target that was not asked for rather than one that failed to build.
+ const QList<QShaderKey> keys = shader.availableShaders();
+
+ #ifndef Q_OS_WASM
+ QVERIFY(keys.contains(QShaderKey(QShader::SpirvShader, QShaderVersion(100))));
+ // Compute shaders and shader storage buffers need 4.3.
+ QVERIFY(keys.contains(QShaderKey(QShader::GlslShader, QShaderVersion(430))));
+ QVERIFY(keys.contains(QShaderKey(QShader::GlslShader, QShaderVersion(420))));
+ QVERIFY(keys.contains(QShaderKey(QShader::GlslShader, QShaderVersion(330))));
+ QVERIFY(keys.contains(QShaderKey(QShader::GlslShader, QShaderVersion(140))));
+ QVERIFY(keys.contains(QShaderKey(QShader::GlslShader, QShaderVersion(130))));
+ QVERIFY(keys.contains(QShaderKey(QShader::GlslShader, QShaderVersion(100, QShaderVersion::GlslEs))));
+ #endif
+ QVERIFY(keys.contains(QShaderKey(QShader::GlslShader, QShaderVersion(300, QShaderVersion::GlslEs))));
+ QVERIFY(keys.contains(QShaderKey(QShader::GlslShader, QShaderVersion(310, QShaderVersion::GlslEs))));
+ // Image atomics need GLSL ES 3.20.
+ QVERIFY(keys.contains(QShaderKey(QShader::GlslShader, QShaderVersion(320, QShaderVersion::GlslEs))));
+ #endif
+ }
+
+ QTEST_APPLESS_MAIN(tst_ShaderCache)
+
+ #include "tst_shadercache.moc"