Revision control
Copy as Markdown
// 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, 460");
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"