Revision control

Copy as Markdown

// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QtTest/QtTest>
#include <QFile>
#include <rhi/qshaderbaker.h>
#include <rhi/qshaderdescription.h>
#include <private/qshader_p.h>
class tst_QShaderBaker : public QObject
{
Q_OBJECT
private slots:
void initTestCase();
void cleanup();
void emptyCompile();
void noFileCompile();
void noTargetsCompile();
void noVariantsCompile();
void simpleCompile();
void simpleCompileNoSpirvSpecified();
void simpleCompileCheckResults();
void simpleCompileFromDevice();
void simpleCompileFromString();
void multiCompile();
void reuse();
void compileError();
void translateError();
void genVariants();
void defines();
void reflectArrayOfStructInBlock();
void reflectCombinedImageSampler();
void mslNativeBindingMap();
void mslArgumentBuffers();
void hlslNativeBindingMap();
void reflectArraysOfSamplers();
void perTargetCompileMode();
void perTargetCompileModeReproducible();
void serializeDeserialize();
void spirvOptions();
void separateImagesAndSamplers();
void tessellationForMetalVertCompile();
void tessellationCompile();
void tessellationCompileWithChangedTessArgs();
void dontBreakOnTranslationError();
void storageImageFlags();
void storageBufferRuntimeArrayStride();
void storageBufferQualifiers();
void multiview();
void mediump();
void geometryShader();
};
void tst_QShaderBaker::initTestCase()
{
}
void tst_QShaderBaker::cleanup()
{
}
void tst_QShaderBaker::emptyCompile()
{
QShaderBaker baker;
QShader s = baker.bake();
QVERIFY(!s.isValid());
QVERIFY(!baker.errorMessage().isEmpty());
qDebug() << baker.errorMessage();
}
void tst_QShaderBaker::noFileCompile()
{
QShaderBaker baker;
baker.setSourceFileName(QLatin1String(":/data/nonexistant.vert"));
QShader s = baker.bake();
QVERIFY(!s.isValid());
QVERIFY(!baker.errorMessage().isEmpty());
qDebug() << baker.errorMessage();
}
void tst_QShaderBaker::noTargetsCompile()
{
QShaderBaker baker;
baker.setSourceFileName(QLatin1String(":/data/color.vert"));
QShader s = baker.bake();
// an empty shader pack is invalid
QVERIFY(!s.isValid());
// not an error from the baker's point of view however
QVERIFY(baker.errorMessage().isEmpty());
}
void tst_QShaderBaker::noVariantsCompile()
{
QShaderBaker baker;
baker.setSourceFileName(QLatin1String(":/data/color.vert"));
QList<QShaderBaker::GeneratedShader> targets;
targets.append({ QShader::SpirvShader, QShaderVersion(100) });
baker.setGeneratedShaders(targets);
QShader s = baker.bake();
// an empty shader pack is invalid
QVERIFY(!s.isValid());
// not an error from the baker's point of view however
QVERIFY(baker.errorMessage().isEmpty());
}
void tst_QShaderBaker::simpleCompile()
{
QShaderBaker baker;
baker.setSourceFileName(QLatin1String(":/data/color.vert"));
baker.setGeneratedShaderVariants({ QShader::StandardShader });
QList<QShaderBaker::GeneratedShader> targets;
targets.append({ QShader::SpirvShader, QShaderVersion(100) });
baker.setGeneratedShaders(targets);
QShader s = baker.bake();
QVERIFY(s.isValid());
QVERIFY(baker.errorMessage().isEmpty());
QCOMPARE(s.availableShaders().size(), 1);
QVERIFY(s.availableShaders().contains(QShaderKey(QShader::SpirvShader, QShaderVersion(100))));
}
void tst_QShaderBaker::simpleCompileNoSpirvSpecified()
{
QShaderBaker baker;
baker.setSourceFileName(QLatin1String(":/data/color.vert"));
baker.setGeneratedShaderVariants({ QShader::StandardShader });
QList<QShaderBaker::GeneratedShader> targets;
targets.append({ QShader::GlslShader, QShaderVersion(330) });
baker.setGeneratedShaders(targets);
QShader s = baker.bake();
QVERIFY(s.isValid());
QVERIFY(baker.errorMessage().isEmpty());
QCOMPARE(s.availableShaders().size(), 1);
QVERIFY(s.availableShaders().contains(QShaderKey(QShader::GlslShader, QShaderVersion(330))));
QVERIFY(s.shader(s.availableShaders().first()).shader().contains(QByteArrayLiteral("#version 330")));
}
void tst_QShaderBaker::simpleCompileCheckResults()
{
QShaderBaker baker;
baker.setSourceFileName(QLatin1String(":/data/color.vert"));
baker.setGeneratedShaderVariants({ QShader::StandardShader });
QList<QShaderBaker::GeneratedShader> targets;
targets.append({ QShader::SpirvShader, QShaderVersion(100) });
baker.setGeneratedShaders(targets);
QShader s = baker.bake();
QVERIFY(s.isValid());
QVERIFY(baker.errorMessage().isEmpty());
QCOMPARE(s.availableShaders().size(), 1);
const QShaderCode shader = s.shader(QShaderKey(QShader::SpirvShader,
QShaderVersion(100)));
QVERIFY(!shader.shader().isEmpty());
QCOMPARE(shader.entryPoint(), QByteArrayLiteral("main"));
const QShaderDescription desc = s.description();
QVERIFY(desc.isValid());
QCOMPARE(desc.inputVariables().size(), 2);
for (const QShaderDescription::InOutVariable &v : desc.inputVariables()) {
switch (v.location) {
case 0:
QCOMPARE(v.name, QByteArrayLiteral("position"));
QCOMPARE(v.type, QShaderDescription::Vec4);
break;
case 1:
QCOMPARE(v.name, QByteArrayLiteral("color"));
QCOMPARE(v.type, QShaderDescription::Vec3);
break;
default:
QFAIL(qPrintable(QStringLiteral("Unexpected location value: %1").arg(v.location)));
break;
}
}
QCOMPARE(desc.outputVariables().size(), 1);
for (const QShaderDescription::InOutVariable &v : desc.outputVariables()) {
switch (v.location) {
case 0:
QCOMPARE(v.name, QByteArrayLiteral("v_color"));
QCOMPARE(v.type, QShaderDescription::Vec3);
break;
default:
QFAIL(qPrintable(QStringLiteral("Unexpected location value: %1").arg(v.location)));
break;
}
}
QCOMPARE(desc.uniformBlocks().size(), 1);
const QShaderDescription::UniformBlock blk = desc.uniformBlocks().first();
QCOMPARE(blk.blockName, QByteArrayLiteral("buf"));
QCOMPARE(blk.structName, QByteArrayLiteral("ubuf"));
QCOMPARE(blk.size, 68);
QCOMPARE(blk.binding, 0);
QCOMPARE(blk.descriptorSet, 0);
QCOMPARE(blk.members.size(), 2);
for (int i = 0; i < blk.members.size(); ++i) {
const QShaderDescription::BlockVariable v = blk.members[i];
switch (i) {
case 0:
QCOMPARE(v.offset, 0);
QCOMPARE(v.size, 64);
QCOMPARE(v.name, QByteArrayLiteral("mvp"));
QCOMPARE(v.type, QShaderDescription::Mat4);
QCOMPARE(v.matrixStride, 16);
break;
case 1:
QCOMPARE(v.offset, 64);
QCOMPARE(v.size, 4);
QCOMPARE(v.name, QByteArrayLiteral("opacity"));
QCOMPARE(v.type, QShaderDescription::Float);
break;
default:
QFAIL(qPrintable(QStringLiteral("Too many blocks").arg(blk.members.size())));
break;
}
}
}
void tst_QShaderBaker::simpleCompileFromDevice()
{
QFile f(QLatin1String(":/data/color.vert"));
QVERIFY(f.open(QIODevice::ReadOnly | QIODevice::Text));
QShaderBaker baker;
baker.setSourceDevice(&f, QShader::VertexStage);
baker.setGeneratedShaderVariants({ QShader::StandardShader });
QList<QShaderBaker::GeneratedShader> targets;
targets.append({ QShader::SpirvShader, QShaderVersion(100) });
baker.setGeneratedShaders(targets);
QShader s = baker.bake();
QVERIFY(s.isValid());
QVERIFY(baker.errorMessage().isEmpty());
QCOMPARE(s.availableShaders().size(), 1);
}
void tst_QShaderBaker::simpleCompileFromString()
{
QFile f(QLatin1String(":/data/color.vert"));
QVERIFY(f.open(QIODevice::ReadOnly | QIODevice::Text));
const QByteArray contents = f.readAll();
f.close();
QVERIFY(!contents.isEmpty());
QShaderBaker baker;
baker.setSourceString(contents, QShader::VertexStage);
baker.setGeneratedShaderVariants({ QShader::StandardShader });
QList<QShaderBaker::GeneratedShader> targets;
targets.append({ QShader::SpirvShader, QShaderVersion(100) });
baker.setGeneratedShaders(targets);
QShader s = baker.bake();
QVERIFY(s.isValid());
QVERIFY(baker.errorMessage().isEmpty());
QCOMPARE(s.availableShaders().size(), 1);
}
void tst_QShaderBaker::multiCompile()
{
QShaderBaker baker;
baker.setSourceFileName(QLatin1String(":/data/color.vert"));
baker.setGeneratedShaderVariants({ QShader::StandardShader });
QList<QShaderBaker::GeneratedShader> targets;
targets.append({ QShader::SpirvShader, QShaderVersion(100) });
targets.append({ QShader::GlslShader, QShaderVersion(100, QShaderVersion::GlslEs) });
targets.append({ QShader::GlslShader, QShaderVersion(120) });
targets.append({ QShader::HlslShader, QShaderVersion(50) });
targets.append({ QShader::MslShader, QShaderVersion(12) });
baker.setGeneratedShaders(targets);
QShader s = baker.bake();
QVERIFY(s.isValid());
QVERIFY(baker.errorMessage().isEmpty());
QCOMPARE(s.availableShaders().size(), 5);
for (const QShaderBaker::GeneratedShader &genShader : targets) {
const QShaderKey key(genShader.first, genShader.second);
const QShaderCode shader = s.shader(key);
QVERIFY(!shader.shader().isEmpty());
if (genShader.first != QShader::MslShader)
QCOMPARE(shader.entryPoint(), QByteArrayLiteral("main"));
}
}
void tst_QShaderBaker::reuse()
{
QShaderBaker baker;
baker.setSourceFileName(QLatin1String(":/data/color.vert"));
baker.setGeneratedShaderVariants({ QShader::StandardShader });
QList<QShaderBaker::GeneratedShader> targets;
targets.append({ QShader::SpirvShader, QShaderVersion(100) });
baker.setGeneratedShaders(targets);
QShader s = baker.bake();
QVERIFY(s.isValid());
QVERIFY(baker.errorMessage().isEmpty());
QCOMPARE(s.availableShaders().size(), 1);
baker.setSourceFileName(QLatin1String(":/data/color.frag"));
targets.clear();
targets.append({ QShader::SpirvShader, QShaderVersion(100) });
targets.append({ QShader::GlslShader, QShaderVersion(100, QShaderVersion::GlslEs) });
targets.append({ QShader::GlslShader, QShaderVersion(120) });
targets.append({ QShader::HlslShader, QShaderVersion(50) });
targets.append({ QShader::MslShader, QShaderVersion(12) });
baker.setGeneratedShaders(targets);
s = baker.bake();
QVERIFY(s.isValid());
QVERIFY(baker.errorMessage().isEmpty());
QCOMPARE(s.availableShaders().size(), 5);
}
void tst_QShaderBaker::compileError()
{
QShaderBaker baker;
baker.setSourceFileName(QLatin1String(":/data/error.vert"));
baker.setGeneratedShaderVariants({ QShader::StandardShader });
QList<QShaderBaker::GeneratedShader> targets;
targets.append({ QShader::SpirvShader, QShaderVersion(100) });
baker.setGeneratedShaders(targets);
QShader s = baker.bake();
QVERIFY(!s.isValid());
QVERIFY(!baker.errorMessage().isEmpty());
qDebug() << baker.errorMessage();
}
void tst_QShaderBaker::translateError()
{
// assume the shader here fails in SPIRV-Cross with "cbuffer cannot be expressed with either HLSL packing layout or packoffset"
//
// This has been improved in SPIRV-Cross, presumably by 0ac65a353e94e9756e8b89fa74e953c482a915e0, so skip this test.
QSKIP("Not applicable with updated SPIRV-Cross");
QShaderBaker baker;
baker.setSourceFileName(QLatin1String(":/data/hlsl_cbuf_error.frag"));
baker.setGeneratedShaderVariants({ QShader::StandardShader });
QList<QShaderBaker::GeneratedShader> targets;
targets.append({ QShader::HlslShader, QShaderVersion(50) });
baker.setGeneratedShaders(targets);
QShader s = baker.bake();
QVERIFY(!s.isValid());
QVERIFY(!baker.errorMessage().isEmpty());
qDebug() << baker.errorMessage();
}
void tst_QShaderBaker::genVariants()
{
QShaderBaker baker;
baker.setSourceFileName(QLatin1String(":/data/color.vert"));
baker.setGeneratedShaderVariants({
QShader::StandardShader,
QShader::BatchableVertexShader
});
QList<QShaderBaker::GeneratedShader> targets;
targets.append({ QShader::SpirvShader, QShaderVersion(100) });
targets.append({ QShader::GlslShader, QShaderVersion(100, QShaderVersion::GlslEs) });
targets.append({ QShader::GlslShader, QShaderVersion(330) });
targets.append({ QShader::GlslShader, QShaderVersion(120) });
targets.append({ QShader::HlslShader, QShaderVersion(50) });
targets.append({ QShader::MslShader, QShaderVersion(12) });
baker.setGeneratedShaders(targets);
QShader s = baker.bake();
QVERIFY(s.isValid());
QVERIFY(baker.errorMessage().isEmpty());
QCOMPARE(s.availableShaders().size(), 2 * 6);
int batchableVariantCount = 0;
int batchableGlslVariantCount = 0;
for (const QShaderKey &key : s.availableShaders()) {
if (key.sourceVariant() == QShader::BatchableVertexShader) {
++batchableVariantCount;
if (key.source() == QShader::GlslShader) {
++batchableGlslVariantCount;
const QByteArray src = s.shader(key).shader();
QVERIFY(src.contains(QByteArrayLiteral("_qt_order * ")));
}
} else {
if (key.source() == QShader::GlslShader) {
// make sure it did not get rewritten
const QByteArray src = s.shader(key).shader();
QVERIFY(!src.contains(QByteArrayLiteral("_qt_order * ")));
}
}
}
QCOMPARE(batchableVariantCount, 6);
QCOMPARE(batchableGlslVariantCount, 3);
}
void tst_QShaderBaker::defines()
{
QShaderBaker baker;
baker.setSourceFileName(QLatin1String(":/data/defines.frag"));
baker.setGeneratedShaderVariants({ QShader::StandardShader });
baker.setGeneratedShaders({ { QShader::SpirvShader, QShaderVersion(100) } });
QShader s = baker.bake();
QVERIFY(!s.isValid());
QVERIFY(!baker.errorMessage().isEmpty());
qDebug() << baker.errorMessage();
QByteArray preamble;
preamble = QByteArrayLiteral("#define DO_NOT_BREAK\n");
baker.setPreamble(preamble);
s = baker.bake();
QVERIFY(s.isValid());
QVERIFY(baker.errorMessage().isEmpty());
QShaderDescription desc = s.description();
QCOMPARE(desc.uniformBlocks().size(), 1);
QShaderDescription::UniformBlock blk = desc.uniformBlocks().first();
QCOMPARE(blk.members.size(), 2);
bool opacity_ok = false;
for (int i = 0; i < blk.members.size(); ++i) {
const QShaderDescription::BlockVariable v = blk.members[i];
if (v.name == QByteArrayLiteral("opacity")) {
opacity_ok = v.type == QShaderDescription::Vec4;
break;
}
}
QVERIFY(opacity_ok);
preamble += QByteArrayLiteral("#define OPACITY_SIZE 1\n");
baker.setPreamble(preamble);
s = baker.bake();
QVERIFY(s.isValid());
QVERIFY(baker.errorMessage().isEmpty());
desc = s.description();
blk = desc.uniformBlocks().first();
opacity_ok = false;
for (int i = 0; i < blk.members.size(); ++i) {
const QShaderDescription::BlockVariable v = blk.members[i];
if (v.name == QByteArrayLiteral("opacity")) {
opacity_ok = v.type == QShaderDescription::Float;
break;
}
}
QVERIFY(opacity_ok);
}
void tst_QShaderBaker::reflectArrayOfStructInBlock()
{
QShaderBaker baker;
baker.setSourceFileName(QLatin1String(":/data/array_of_struct_in_ubuf.frag"));
baker.setGeneratedShaderVariants({ QShader::StandardShader });
QList<QShaderBaker::GeneratedShader> targets;
targets.append({ QShader::SpirvShader, QShaderVersion(100) });
targets.append({ QShader::GlslShader, QShaderVersion(100, QShaderVersion::GlslEs) });
targets.append({ QShader::GlslShader, QShaderVersion(120) });
targets.append({ QShader::GlslShader, QShaderVersion(150) });
targets.append({ QShader::HlslShader, QShaderVersion(50) });
targets.append({ QShader::MslShader, QShaderVersion(12) });
baker.setGeneratedShaders(targets);
QShader s = baker.bake();
QVERIFY(s.isValid());
QVERIFY(baker.errorMessage().isEmpty());
QShaderDescription desc = s.description();
QCOMPARE(desc.inputVariables().size(), 3);
QCOMPARE(desc.outputVariables().size(), 1);
QCOMPARE(desc.uniformBlocks().size(), 1);
const QList<QShaderDescription::InOutVariable> inputs = desc.inputVariables();
for (const auto &var : inputs) {
switch (var.location) {
case 0:
QCOMPARE(var.name, QByteArrayLiteral("vECVertNormal"));
QCOMPARE(var.type, QShaderDescription::Vec3);
break;
case 1:
QCOMPARE(var.name, QByteArrayLiteral("vECVertPos"));
QCOMPARE(var.type, QShaderDescription::Vec3);
break;
case 2:
QCOMPARE(var.name, QByteArrayLiteral("vDiffuseAdjust"));
QCOMPARE(var.type, QShaderDescription::Vec3);
break;
default:
QFAIL(qPrintable(QStringLiteral("Unexpected input variable: %1").arg(var.location)));
return;
}
}
const QList<QShaderDescription::InOutVariable> outputs = desc.outputVariables();
QCOMPARE(outputs.first().location, 0);
QCOMPARE(outputs.first().name, QByteArrayLiteral("fragColor"));
QCOMPARE(outputs.first().type, QShaderDescription::Vec4);
const QList<QShaderDescription::UniformBlock> ublocks = desc.uniformBlocks();
const QShaderDescription::UniformBlock ub = ublocks.first();
QCOMPARE(ub.binding, 1);
QCOMPARE(ub.blockName, QByteArrayLiteral("buf"));
QCOMPARE(ub.structName, QByteArrayLiteral("ubuf"));
QCOMPARE(ub.size, 768);
QCOMPARE(ub.members.size(), 7);
for (const QShaderDescription::BlockVariable &var : ub.members) {
if (var.name == QByteArrayLiteral("ECCameraPosition")) {
QCOMPARE(var.offset, 0);
QCOMPARE(var.size, 12);
QCOMPARE(var.type, QShaderDescription::Vec3);
} else if (var.name == QByteArrayLiteral("ka")) {
QCOMPARE(var.offset, 16);
QCOMPARE(var.size, 12);
QCOMPARE(var.type, QShaderDescription::Vec3);
} else if (var.name == QByteArrayLiteral("kd")) {
QCOMPARE(var.offset, 32);
QCOMPARE(var.size, 12);
QCOMPARE(var.type, QShaderDescription::Vec3);
} else if (var.name == QByteArrayLiteral("ks")) {
QCOMPARE(var.offset, 48);
QCOMPARE(var.size, 12);
QCOMPARE(var.type, QShaderDescription::Vec3);
} else if (var.name == QByteArrayLiteral("numLights")) {
QCOMPARE(var.offset, 704);
QCOMPARE(var.size, 4);
QCOMPARE(var.type, QShaderDescription::Int);
} else if (var.name == QByteArrayLiteral("mm")) {
QCOMPARE(var.offset, 720);
QCOMPARE(var.size, 48);
QCOMPARE(var.type, QShaderDescription::Mat3);
QCOMPARE(var.matrixStride, 16);
QCOMPARE(var.matrixIsRowMajor, true);
} else if (var.name == QByteArrayLiteral("lights")) {
QCOMPARE(var.offset, 64);
QCOMPARE(var.size, 640);
QCOMPARE(var.type, QShaderDescription::Struct);
QCOMPARE(var.arrayDims, QList<int>() << 10);
QCOMPARE(var.structMembers.size(), 8);
for (const QShaderDescription::BlockVariable &structVar : var.structMembers) {
if (structVar.name == QByteArrayLiteral("ECLightPosition")) {
QCOMPARE(structVar.offset, 0);
QCOMPARE(structVar.size, 12);
QCOMPARE(structVar.type, QShaderDescription::Vec3);
} else if (structVar.name == QByteArrayLiteral("attenuation")) {
QCOMPARE(structVar.offset, 16);
QCOMPARE(structVar.size, 12);
QCOMPARE(structVar.type, QShaderDescription::Vec3);
} else if (structVar.name == QByteArrayLiteral("color")) {
QCOMPARE(structVar.offset, 32);
QCOMPARE(structVar.size, 12);
QCOMPARE(structVar.type, QShaderDescription::Vec3);
} else if (structVar.name == QByteArrayLiteral("intensity")) {
QCOMPARE(structVar.offset, 48);
QCOMPARE(structVar.size, 4);
QCOMPARE(structVar.type, QShaderDescription::Float);
} else if (structVar.name == QByteArrayLiteral("specularExp")) {
QCOMPARE(structVar.offset, 52);
QCOMPARE(structVar.size, 4);
QCOMPARE(structVar.type, QShaderDescription::Float);
} else if (structVar.name == QByteArrayLiteral("padding1")) {
QCOMPARE(structVar.offset, 12);
QCOMPARE(structVar.size, 4);
QCOMPARE(structVar.type, QShaderDescription::Float);
} else if (structVar.name == QByteArrayLiteral("padding2")) {
QCOMPARE(structVar.offset, 28);
QCOMPARE(structVar.size, 4);
QCOMPARE(structVar.type, QShaderDescription::Float);
} else if (structVar.name == QByteArrayLiteral("padding3")) {
QCOMPARE(structVar.offset, 44);
QCOMPARE(structVar.size, 4);
QCOMPARE(structVar.type, QShaderDescription::Float);
} else {
QFAIL(qPrintable(
QStringLiteral(
"Unexpected member in 'lights' struct in uniform block: %1")
.arg(structVar.name)));
}
}
} else {
QFAIL(qPrintable(QStringLiteral("Unexpected uniform block member: %1").arg(var.name)));
}
}
}
void tst_QShaderBaker::reflectCombinedImageSampler()
{
QShaderBaker baker;
baker.setSourceFileName(QLatin1String(":/data/sgtexture.frag"));
baker.setGeneratedShaderVariants({ QShader::StandardShader });
QList<QShaderBaker::GeneratedShader> targets;
targets.append({ QShader::SpirvShader, QShaderVersion(100) });
targets.append({ QShader::GlslShader, QShaderVersion(100, QShaderVersion::GlslEs) });
targets.append({ QShader::GlslShader, QShaderVersion(120) });
targets.append({ QShader::GlslShader, QShaderVersion(150) });
targets.append({ QShader::HlslShader, QShaderVersion(50) });
targets.append({ QShader::MslShader, QShaderVersion(12) });
baker.setGeneratedShaders(targets);
QShader s = baker.bake();
QVERIFY(s.isValid());
QVERIFY(baker.errorMessage().isEmpty());
QShaderDescription desc = s.description();
QCOMPARE(desc.inputVariables().size(), 1);
QCOMPARE(desc.outputVariables().size(), 1);
QCOMPARE(desc.uniformBlocks().size(), 1);
QCOMPARE(desc.combinedImageSamplers().size(), 2);
auto inputVar = desc.inputVariables().first();
QCOMPARE(inputVar.location, 0);
QCOMPARE(inputVar.name, QByteArrayLiteral("qt_TexCoord"));
QCOMPARE(inputVar.type, QShaderDescription::Vec2);
auto outputVar = desc.outputVariables().first();
QCOMPARE(outputVar.location, 0);
QCOMPARE(outputVar.name, QByteArrayLiteral("fragColor"));
QCOMPARE(outputVar.type, QShaderDescription::Vec4);
auto block = desc.uniformBlocks().first();
QCOMPARE(block.binding, 0);
QCOMPARE(block.size, 68);
QCOMPARE(block.blockName, QByteArrayLiteral("buf"));
QCOMPARE(block.structName, QByteArrayLiteral("ubuf"));
QCOMPARE(block.members.size(), 2);
for (int i = 0; i < block.members.size(); ++i) {
const QShaderDescription::BlockVariable &blockVar(block.members[i]);
switch (i) {
case 0:
QCOMPARE(blockVar.name, QByteArrayLiteral("qt_Matrix"));
QCOMPARE(blockVar.offset, 0);
QCOMPARE(blockVar.size, 64);
QCOMPARE(blockVar.type, QShaderDescription::Mat4);
QCOMPARE(blockVar.matrixStride, 16);
QCOMPARE(blockVar.matrixIsRowMajor, false);
break;
case 1:
QCOMPARE(blockVar.name, QByteArrayLiteral("opacity"));
QCOMPARE(blockVar.offset, 64);
QCOMPARE(blockVar.size, 4);
QCOMPARE(blockVar.type, QShaderDescription::Float);
break;
default:
break;
}
}
for (const QShaderDescription::InOutVariable &imSampVar : desc.combinedImageSamplers()) {
switch (imSampVar.binding) {
case 1:
QCOMPARE(imSampVar.name, QByteArrayLiteral("qt_Texture"));
QCOMPARE(imSampVar.type, QShaderDescription::Sampler2D);
break;
case 2:
QCOMPARE(imSampVar.name, QByteArrayLiteral("t1"));
QCOMPARE(imSampVar.type, QShaderDescription::Sampler2D);
break;
default:
QFAIL(qPrintable(QStringLiteral("Unexpected combined image sampler: %1")
.arg(imSampVar.binding)));
return;
}
}
}
void tst_QShaderBaker::mslNativeBindingMap()
{
QShaderBaker baker;
baker.setSourceFileName(QLatin1String(":/data/sgtexture.frag"));
baker.setGeneratedShaderVariants({ QShader::StandardShader });
QList<QShaderBaker::GeneratedShader> targets;
targets.append({ QShader::SpirvShader, QShaderVersion(100) });
targets.append({ QShader::GlslShader, QShaderVersion(100, QShaderVersion::GlslEs) });
targets.append({ QShader::GlslShader, QShaderVersion(120) });
targets.append({ QShader::GlslShader, QShaderVersion(150) });
targets.append({ QShader::HlslShader, QShaderVersion(50) });
targets.append({ QShader::MslShader, QShaderVersion(12) });
baker.setGeneratedShaders(targets);
QShader s = baker.bake();
QVERIFY(s.isValid());
QVERIFY(baker.errorMessage().isEmpty());
const QShaderKey mslShaderKey(QShader::MslShader, QShaderVersion(12));
QVERIFY(!s.nativeResourceBindingMap(mslShaderKey).isEmpty());
QShader::NativeResourceBindingMap nativeBindingMap = s.nativeResourceBindingMap(mslShaderKey);
QCOMPARE(nativeBindingMap.size(), 3);
QVERIFY(nativeBindingMap.contains(0)); // uniform block
QVERIFY(nativeBindingMap.contains(1)); // combined image sampler, maps to a texture and sampler in MSL
QVERIFY(nativeBindingMap.contains(2)); // same
// MSL has per-resource namespaces
std::pair<int, int> nativeBindingPair = nativeBindingMap.value(0);
QCOMPARE(nativeBindingPair.first, 0); // uniform buffer
nativeBindingPair = nativeBindingMap.value(1);
QCOMPARE(nativeBindingPair.first, 0); // texture
QCOMPARE(nativeBindingPair.second, 0); // sampler
nativeBindingPair = nativeBindingMap.value(2);
QCOMPARE(nativeBindingPair.first, 1); // texture
QCOMPARE(nativeBindingPair.second, 1); // sampler
baker.setSourceFileName(QLatin1String(":/data/manyresources.frag"));
targets.clear();
targets.append({ QShader::SpirvShader, QShaderVersion(100) });
targets.append({ QShader::MslShader, QShaderVersion(12) });
baker.setGeneratedShaders(targets);
s = baker.bake();
QVERIFY(s.isValid());
QVERIFY(baker.errorMessage().isEmpty());
nativeBindingMap = s.nativeResourceBindingMap(mslShaderKey);
QCOMPARE(nativeBindingMap.size(), 8);
// rather won't compare with exact values, do not make assumptions, just
// make sure there is a valid value for each since all resources are used
// in the shader code
QVERIFY(nativeBindingMap.value(1).first != -1);
QVERIFY(nativeBindingMap.value(2).first != -1);
QVERIFY(nativeBindingMap.value(3).first != -1);
QVERIFY(nativeBindingMap.value(4).first != -1);
QVERIFY(nativeBindingMap.value(5).first != -1);
QVERIFY(nativeBindingMap.value(10).first != -1);
QVERIFY(nativeBindingMap.value(11).first != -1);
QVERIFY(nativeBindingMap.value(12).first != -1);
}
void tst_QShaderBaker::mslArgumentBuffers()
{
QShaderBaker baker;
baker.setSourceFileName(QLatin1String(":/data/sgtexture.frag"));
baker.setGeneratedShaderVariants({ QShader::StandardShader, QShader::ArgumentBufferShader });
QList<QShaderBaker::GeneratedShader> targets;
targets.append({ QShader::SpirvShader, QShaderVersion(100) });
targets.append({ QShader::GlslShader, QShaderVersion(150) });
targets.append({ QShader::MslShader, QShaderVersion(21) });
baker.setGeneratedShaders(targets);
QShader s = baker.bake();
QVERIFY(s.isValid());
QVERIFY(baker.errorMessage().isEmpty());
// the variant is MSL-only
QCOMPARE(s.availableShaders().size(), 4);
const QShaderKey stdKey(QShader::MslShader, QShaderVersion(21));
const QShaderKey argBufKey(QShader::MslShader, QShaderVersion(21), QShader::ArgumentBufferShader);
QVERIFY(s.availableShaders().contains(stdKey));
QVERIFY(s.availableShaders().contains(argBufKey));
const QByteArray argBufSource = s.shader(argBufKey).shader();
QVERIFY(argBufSource.contains(QByteArrayLiteral("struct spvDescriptorSetBuffer1")));
QVERIFY(argBufSource.contains(QByteArrayLiteral("[[id(0)]]")));
// buffers stay bound individually
QVERIFY(argBufSource.contains(QByteArrayLiteral("constant buf& ubuf [[buffer(1)]]")));
QVERIFY(!s.shader(stdKey).shader().contains(QByteArrayLiteral("spvDescriptorSetBuffer")));
const int argBufIndex = s.nativeShaderInfo(argBufKey).extraBufferBindings.value(
QShaderPrivate::MslArgumentBufferBinding, -1);
QCOMPARE(argBufIndex, 0);
QVERIFY(!s.nativeShaderInfo(stdKey).extraBufferBindings.contains(QShaderPrivate::MslArgumentBufferBinding));
// the combined image samplers now name slots in the argument buffer, and
// the uniform block has moved up to make room for the argument buffer
QShader::NativeResourceBindingMap map = s.nativeResourceBindingMap(argBufKey);
QCOMPARE(map.size(), 3);
QCOMPARE(map.value(0).first, 1);
QCOMPARE(map.value(1), std::make_pair(0, 1));
QCOMPARE(map.value(2), std::make_pair(2, 3));
map = s.nativeResourceBindingMap(stdKey);
QCOMPARE(map.value(0).first, 0);
QCOMPARE(map.value(1), std::make_pair(0, 0));
// no images or samplers, so no argument buffer and no extra key
baker.setSourceFileName(QLatin1String(":/data/color.vert"));
s = baker.bake();
QVERIFY(s.isValid());
QVERIFY(!s.availableShaders().contains(argBufKey));
// below MSL 2.0 the variant cannot be generated
baker.setSourceFileName(QLatin1String(":/data/sgtexture.frag"));
targets.clear();
targets.append({ QShader::SpirvShader, QShaderVersion(100) });
targets.append({ QShader::MslShader, QShaderVersion(12) });
baker.setGeneratedShaders(targets);
s = baker.bake();
QVERIFY(s.isValid());
QVERIFY(!s.availableShaders().contains(QShaderKey(QShader::MslShader, QShaderVersion(12),
QShader::ArgumentBufferShader)));
// arrays keep their declared size, and the texture and sampler slots of an
// array stay that many ids apart
baker.setSourceFileName(QLatin1String(":/data/arrays_of_samplers.frag"));
targets.clear();
targets.append({ QShader::SpirvShader, QShaderVersion(100) });
targets.append({ QShader::MslShader, QShaderVersion(21) });
baker.setGeneratedShaders(targets);
s = baker.bake();
QVERIFY(s.isValid());
QVERIFY(s.availableShaders().contains(argBufKey));
QVERIFY(s.shader(argBufKey).shader().contains(
QByteArrayLiteral("array<texture2d<float>, 4> shadowMaps")));
map = s.nativeResourceBindingMap(argBufKey);
QCOMPARE(map.value(8).second - map.value(8).first, 4);
QCOMPARE(map.value(9).second - map.value(9).first, 4);
}
void tst_QShaderBaker::hlslNativeBindingMap()
{
QShaderBaker baker;
baker.setSourceFileName(QLatin1String(":/data/manyresources.frag"));
baker.setGeneratedShaderVariants({ QShader::StandardShader });
QList<QShaderBaker::GeneratedShader> targets;
targets.append({ QShader::SpirvShader, QShaderVersion(100) });
targets.append({ QShader::HlslShader, QShaderVersion(50) });
baker.setGeneratedShaders(targets);
QShader s = baker.bake();
QVERIFY(s.isValid());
QVERIFY(baker.errorMessage().isEmpty());
const QShaderKey shaderKey(QShader::HlslShader, QShaderVersion(50));
QShader::NativeResourceBindingMap nativeBindingMap = s.nativeResourceBindingMap(shaderKey);
QCOMPARE(nativeBindingMap.size(), 8);
// do not assume anything about the ordering, so verify that we have valid
// values, not the register binding values themselves
// combined image samplers
QVERIFY(nativeBindingMap.value(1).first != -1);
QVERIFY(nativeBindingMap.value(1).second == nativeBindingMap.value(1).first);
QVERIFY(nativeBindingMap.value(2).first != -1);
QVERIFY(nativeBindingMap.value(2).second == nativeBindingMap.value(2).first);
QVERIFY(nativeBindingMap.value(3).first != -1);
QVERIFY(nativeBindingMap.value(3).second == nativeBindingMap.value(3).first);
QVERIFY(nativeBindingMap.value(1).first != nativeBindingMap.value(2).first);
QVERIFY(nativeBindingMap.value(1).first != nativeBindingMap.value(3).first);
// storage images
QVERIFY(nativeBindingMap.value(4).first != -1);
QVERIFY(nativeBindingMap.value(5).first != -1);
QVERIFY(nativeBindingMap.value(4).first != nativeBindingMap.value(5).first);
// storage buffer
QVERIFY(nativeBindingMap.value(10).first != -1);
// uniform buffers
QVERIFY(nativeBindingMap.value(11).first != -1);
QVERIFY(nativeBindingMap.value(12).first != -1);
QVERIFY(nativeBindingMap.value(11).first != nativeBindingMap.value(12).first);
}
void tst_QShaderBaker::reflectArraysOfSamplers()
{
QShaderBaker baker;
baker.setSourceFileName(QLatin1String(":/data/arrays_of_samplers.frag"));
baker.setGeneratedShaderVariants({ QShader::StandardShader });
QList<QShaderBaker::GeneratedShader> targets;
targets.append({ QShader::SpirvShader, QShaderVersion(100) });
targets.append({ QShader::GlslShader, QShaderVersion(100, QShaderVersion::GlslEs) });
targets.append({ QShader::GlslShader, QShaderVersion(120) });
targets.append({ QShader::GlslShader, QShaderVersion(150) });
targets.append({ QShader::HlslShader, QShaderVersion(50) });
targets.append({ QShader::MslShader, QShaderVersion(20) }); // arrays of textures needs MSL 2.0
baker.setGeneratedShaders(targets);
QShader s = baker.bake();
QVERIFY(baker.errorMessage().isEmpty());
QVERIFY(s.isValid());
QShaderDescription desc = s.description();
QCOMPARE(desc.inputVariables().size(), 1);
QCOMPARE(desc.outputVariables().size(), 1);
QCOMPARE(desc.combinedImageSamplers().size(), 4);
for (const QShaderDescription::InOutVariable &var : desc.combinedImageSamplers()) {
switch (var.binding) {
case 4:
QCOMPARE(var.type, QShaderDescription::Sampler2D);
QVERIFY(var.arrayDims.isEmpty());
break;
case 8:
QCOMPARE(var.type, QShaderDescription::Sampler2D);
QCOMPARE(var.arrayDims.size(), 1);
QCOMPARE(var.arrayDims.first(), 4);
break;
case 9:
QCOMPARE(var.type, QShaderDescription::SamplerCube);
QCOMPARE(var.arrayDims.size(), 1);
QCOMPARE(var.arrayDims.first(), 4);
break;
case 10:
QCOMPARE(var.type, QShaderDescription::Sampler2DArray);
QVERIFY(var.arrayDims.isEmpty());
break;
default:
QFAIL(qPrintable(QStringLiteral("Wrong binding value: %1").arg(var.binding)));
}
}
}
void tst_QShaderBaker::perTargetCompileMode()
{
QShaderBaker baker;
baker.setSourceFileName(QLatin1String(":/data/color_pertarget.vert"));
baker.setGeneratedShaderVariants({
QShader::StandardShader,
QShader::BatchableVertexShader
});
QList<QShaderBaker::GeneratedShader> targets;
targets.append({ QShader::SpirvShader, QShaderVersion(100) });
targets.append({ QShader::GlslShader, QShaderVersion(100, QShaderVersion::GlslEs) });
targets.append({ QShader::GlslShader, QShaderVersion(330) });
targets.append({ QShader::GlslShader, QShaderVersion(120) });
targets.append({ QShader::HlslShader, QShaderVersion(50) });
targets.append({ QShader::MslShader, QShaderVersion(12) });
baker.setGeneratedShaders(targets);
baker.setPreamble(QByteArrayLiteral("#define MY_VALUE 321\n#define MY_MACRO\n"));
baker.setPerTargetCompilation(true);
QShader s = baker.bake();
if (!s.isValid())
qDebug() << baker.errorMessage();
QVERIFY(s.isValid());
QVERIFY(baker.errorMessage().isEmpty());
QCOMPARE(s.availableShaders().size(), 2 * 6);
int batchableVariantCount = 0;
int batchableGlslVariantCount = 0;
for (const QShaderKey &key : s.availableShaders()) {
if (key.sourceVariant() == QShader::BatchableVertexShader) {
++batchableVariantCount;
if (key.source() == QShader::GlslShader) {
++batchableGlslVariantCount;
const QByteArray src = s.shader(key).shader();
QVERIFY(src.contains(QByteArrayLiteral("_qt_order * ")));
}
} else {
if (key.source() == QShader::GlslShader) {
// make sure it did not get rewritten
const QByteArray src = s.shader(key).shader();
QVERIFY(!src.contains(QByteArrayLiteral("_qt_order * ")));
}
}
}
QCOMPARE(batchableVariantCount, 6);
QCOMPARE(batchableGlslVariantCount, 3);
// now compile something that succeeds only if the QSHADER_SPIRV macros are
// present as expected
baker.setSourceFileName(QLatin1String(":/data/color_spirv_only.vert"));
baker.setGeneratedShaderVariants({ QShader::StandardShader });
targets.clear();
targets.append({ QShader::SpirvShader, QShaderVersion(100) });
baker.setGeneratedShaders(targets);
baker.setPreamble(QByteArray());
s = baker.bake();
if (!s.isValid())
qDebug() << baker.errorMessage();
QVERIFY(s.isValid());
QVERIFY(baker.errorMessage().isEmpty());
QCOMPARE(s.availableShaders().size(), 1);
// now something similar, with GLSL. This will mean no QSHADER_SPIRV,
// QSHADER_HLSL, QSHADER_MSL are defined (and is an error if they are)
baker.setSourceFileName(QLatin1String(":/data/color_glsl_only.vert"));
baker.setGeneratedShaderVariants({ QShader::StandardShader });
targets.clear();
targets.append({ QShader::GlslShader, QShaderVersion(330) });
targets.append({ QShader::GlslShader, QShaderVersion(440) });
baker.setGeneratedShaders(targets);
s = baker.bake();
if (!s.isValid())
qDebug() << baker.errorMessage();
QVERIFY(s.isValid());
QVERIFY(baker.errorMessage().isEmpty());
QCOMPARE(s.availableShaders().size(), 2);
// HLSL 5.0 + MSL 1.2 (fails with others)
baker.setSourceFileName(QLatin1String(":/data/color_hlsl_msl_only.vert"));
baker.setGeneratedShaderVariants({ QShader::StandardShader });
targets.clear();
targets.append({ QShader::HlslShader, QShaderVersion(50) });
targets.append({ QShader::MslShader, QShaderVersion(12) });
baker.setGeneratedShaders(targets);
s = baker.bake();
if (!s.isValid())
qDebug() << baker.errorMessage();
QVERIFY(s.isValid());
QVERIFY(baker.errorMessage().isEmpty());
QCOMPARE(s.availableShaders().size(), 2);
}
void tst_QShaderBaker::perTargetCompileModeReproducible()
{
// In per-target compilation mode there is one SPIR-V binary per target, and
// the single QShaderDescription of the resulting QShader has to be generated
// from one of them. Which one that is must not vary between bakes, not even
// when the reflection info differs per target (which it can, when the source
// declares its resources based on the QSHADER_* macros).
QShaderBaker baker;
baker.setSourceFileName(QLatin1String(":/data/color_pertarget_reflect.vert"));
baker.setGeneratedShaderVariants({
QShader::StandardShader,
QShader::BatchableVertexShader
});
QList<QShaderBaker::GeneratedShader> targets;
targets.append({ QShader::SpirvShader, QShaderVersion(100) });
targets.append({ QShader::GlslShader, QShaderVersion(100, QShaderVersion::GlslEs) });
targets.append({ QShader::GlslShader, QShaderVersion(330) });
targets.append({ QShader::HlslShader, QShaderVersion(50) });
targets.append({ QShader::MslShader, QShaderVersion(12) });
baker.setGeneratedShaders(targets);
baker.setPerTargetCompilation(true);
QByteArray reference;
for (int i = 0; i < 8; ++i) {
// Get a different QHash seed on every round. Picking the SPIR-V binary
// to reflect on out of a QHash would make the result depend on this.
QHashSeed::resetRandomGlobalSeed();
QShader s = baker.bake();
if (!s.isValid())
qDebug() << baker.errorMessage();
QVERIFY(s.isValid());
QVERIFY(baker.errorMessage().isEmpty());
QCOMPARE(s.availableShaders().size(), 2 * 5);
// The plain SPIR-V target is the one that wins, so the uniform block
// must have the single mat4 member and nothing else.
const QList<QShaderDescription::UniformBlock> blocks = s.description().uniformBlocks();
QCOMPARE(blocks.size(), 1);
QCOMPARE(blocks[0].members.size(), 1);
QCOMPARE(blocks[0].members[0].name, QByteArrayLiteral("mvp"));
// and the whole thing must serialize to the exact same bytes every time
const QByteArray serialized = s.serialized();
QVERIFY(!serialized.isEmpty());
if (reference.isEmpty())
reference = serialized;
else
QCOMPARE(serialized, reference);
}
}
void tst_QShaderBaker::serializeDeserialize()
{
QShaderBaker baker;
baker.setSourceFileName(QLatin1String(":/data/color.vert"));
baker.setGeneratedShaderVariants({ QShader::StandardShader });
QList<QShaderBaker::GeneratedShader> targets;
targets.append({ QShader::SpirvShader, QShaderVersion(100) });
targets.append({ QShader::GlslShader, QShaderVersion(100, QShaderVersion::GlslEs) });
targets.append({ QShader::GlslShader, QShaderVersion(120) });
targets.append({ QShader::HlslShader, QShaderVersion(50) });
targets.append({ QShader::MslShader, QShaderVersion(12) });
baker.setGeneratedShaders(targets);
QShader s = baker.bake();
QVERIFY(s.isValid());
QVERIFY(baker.errorMessage().isEmpty());
QCOMPARE(s.availableShaders().size(), 5);
QByteArray data = s.serialized();
QVERIFY(!data.isEmpty());
QShader s2;
QVERIFY(!s2.isValid());
s2 = QShader::fromSerialized(data);
QVERIFY(s2.isValid());
QCOMPARE(s, s2);
}
void tst_QShaderBaker::spirvOptions()
{
QShaderBaker baker;
baker.setSourceFileName(QLatin1String(":/data/color.vert"));
baker.setGeneratedShaderVariants({ QShader::StandardShader });
QList<QShaderBaker::GeneratedShader> targets;
targets.append({ QShader::SpirvShader, QShaderVersion(100) });
baker.setGeneratedShaders(targets);
// no options specified (no full debug info, no strip)
QShader s = baker.bake();
QVERIFY(s.isValid());
QVERIFY(baker.errorMessage().isEmpty());
QCOMPARE(s.availableShaders().size(), 1);
const QShaderKey key(QShader::SpirvShader, QShaderVersion(100));
QVERIFY(s.availableShaders().contains(key));
QCOMPARE(s.shader(key).entryPoint(), "main");
QByteArray bin = s.shader(key).shader();
// full debug info
baker.setSpirvOptions(QShaderBaker::SpirvOption::GenerateFullDebugInfo);
s = baker.bake();
QVERIFY(s.isValid());
QVERIFY(baker.errorMessage().isEmpty());
QCOMPARE(s.availableShaders().size(), 1);
QCOMPARE(s.shader(key).entryPoint(), "main");
QByteArray debugBin = s.shader(key).shader();
QVERIFY(bin != debugBin);
QVERIFY(debugBin.size() > bin.size());
// strip all (variable names, etc.)
baker.setSpirvOptions(QShaderBaker::SpirvOption::StripDebugAndVarInfo);
s = baker.bake();
QVERIFY(s.isValid());
QVERIFY(baker.errorMessage().isEmpty());
QCOMPARE(s.availableShaders().size(), 1);
QCOMPARE(s.shader(key).entryPoint(), "main");
QByteArray strippedBin = s.shader(key).shader();
QVERIFY(strippedBin != debugBin);
QVERIFY(debugBin.size() > strippedBin.size());
QVERIFY(strippedBin != bin);
QVERIFY(bin.size() > strippedBin.size());
}
void tst_QShaderBaker::separateImagesAndSamplers()
{
QShaderBaker baker;
baker.setSourceFileName(QLatin1String(":/data/combined_and_separate_samplers.frag"));
baker.setGeneratedShaderVariants({ QShader::StandardShader });
QList<QShaderBaker::GeneratedShader> targets;
targets.append({ QShader::SpirvShader, QShaderVersion(100) });
targets.append({ QShader::GlslShader, QShaderVersion(100, QShaderVersion::GlslEs) });
targets.append({ QShader::GlslShader, QShaderVersion(330) });
targets.append({ QShader::HlslShader, QShaderVersion(50) });
targets.append({ QShader::MslShader, QShaderVersion(12) });
baker.setGeneratedShaders(targets);
QShader s = baker.bake();
QVERIFY(s.isValid());
QVERIFY(baker.errorMessage().isEmpty());
for (QShaderKey shaderKey : {
QShaderKey(QShader::HlslShader, QShaderVersion(50)),
QShaderKey(QShader::MslShader, QShaderVersion(12)) })
{
QShader::NativeResourceBindingMap nativeBindingMap = s.nativeResourceBindingMap(shaderKey);
QCOMPARE(nativeBindingMap.size(), 4);
// binding 1 is a combined image sampler
QVERIFY(nativeBindingMap.value(1).first != -1);
QVERIFY(nativeBindingMap.value(1).second != -1);
// binding 2 is a separate image
QVERIFY(nativeBindingMap.value(2).first != -1);
// presumably binding 1 got t0, then this is probably t1, the point is that they should be something different
QVERIFY(nativeBindingMap.value(2).first != nativeBindingMap.value(1).first);
// binding 3 is a separate sampler
QVERIFY(nativeBindingMap.value(3).first != -1);
// binding 4 as well
QVERIFY(nativeBindingMap.value(4).first != -1);
QVERIFY(nativeBindingMap.value(4).first != nativeBindingMap.value(3).first);
QVERIFY(s.separateToCombinedImageSamplerMappingList(shaderKey).isEmpty());
}
// Now onto to the real special deal here: OpenGL. That has its own mapping
// table because SPIRV-Cross auto-generates a "combined image sampler"
// (e.g. a sampler2D) for each separate texture+sampler combination.
for (QShaderKey shaderKey : {
QShaderKey(QShader::GlslShader, QShaderVersion(330)),
QShaderKey(QShader::GlslShader, QShaderVersion(100, QShaderVersion::GlslEs)) })
{
QShader::SeparateToCombinedImageSamplerMappingList list = s.separateToCombinedImageSamplerMappingList(shaderKey);
QCOMPARE(list.size(), 2);
for (int i = 0; i < list.size(); ++i) {
int tbinding = list.at(i).textureBinding;
int sbinding = list.at(i).samplerBinding;
QCOMPARE(tbinding, 2);
QVERIFY(sbinding == 3 || sbinding == 4);
QByteArray combinedName = list.at(i).combinedSamplerName;
QVERIFY(!combinedName.isEmpty());
}
QVERIFY(s.nativeResourceBindingMap(shaderKey).isEmpty());
}
}
void tst_QShaderBaker::tessellationForMetalVertCompile()
{
QShaderBaker baker;
baker.setSourceFileName(QLatin1String(":/data/color.vert"));
baker.setGeneratedShaderVariants({ QShader::StandardShader,
QShader::NonIndexedVertexAsComputeShader,
QShader::UInt16IndexedVertexAsComputeShader,
QShader::UInt32IndexedVertexAsComputeShader });
QList<QShaderBaker::GeneratedShader> targets;
targets.append({ QShader::SpirvShader, QShaderVersion(100) });
targets.append({ QShader::GlslShader, QShaderVersion(430) });
targets.append({ QShader::MslShader, QShaderVersion(12) });
baker.setGeneratedShaders(targets);
QShader s = baker.bake();
QVERIFY(s.isValid());
QVERIFY(baker.errorMessage().isEmpty());
QVERIFY(s.stage() == QShader::VertexStage);
QCOMPARE(s.availableShaders().size(), 6);
QVERIFY(!s.shader(QShaderKey(QShader::MslShader, QShaderVersion(12), QShader::NonIndexedVertexAsComputeShader)).shader().isEmpty());
QVERIFY(!s.shader(QShaderKey(QShader::MslShader, QShaderVersion(12), QShader::UInt16IndexedVertexAsComputeShader)).shader().isEmpty());
QVERIFY(!s.shader(QShaderKey(QShader::MslShader, QShaderVersion(12), QShader::UInt32IndexedVertexAsComputeShader)).shader().isEmpty());
QShaderDescription desc = s.description();
QCOMPARE(desc.outputBuiltinVariables().size(), 1);
QCOMPARE(desc.outputBuiltinVariables()[0].type, QShaderDescription::PositionBuiltin);
QShader::NativeShaderInfo info = s.nativeShaderInfo(QShaderKey(QShader::MslShader, QShaderVersion(12), QShader::NonIndexedVertexAsComputeShader));
QCOMPARE(info.extraBufferBindings.size(), 1);
QVERIFY(info.extraBufferBindings.value(QShaderPrivate::MslTessVertTescOutputBufferBinding, -1) >= 0);
info = s.nativeShaderInfo(QShaderKey(QShader::MslShader, QShaderVersion(12), QShader::UInt32IndexedVertexAsComputeShader));
QCOMPARE(info.extraBufferBindings.size(), 2);
const int outVertBufIndex = info.extraBufferBindings.value(QShaderPrivate::MslTessVertTescOutputBufferBinding, -1);
QVERIFY(outVertBufIndex >= 0);
QVERIFY(info.extraBufferBindings.value(QShaderPrivate::MslTessVertIndicesBufferBinding, -1) >= 0);
QVERIFY(info.extraBufferBindings.value(QShaderPrivate::MslTessVertIndicesBufferBinding, -1) != outVertBufIndex);
}
void tst_QShaderBaker::tessellationCompile()
{
QShaderBaker baker;
baker.setSourceFileName(QLatin1String(":/data/tess.tesc"));
baker.setGeneratedShaderVariants({ QShader::StandardShader });
QList<QShaderBaker::GeneratedShader> targets;
targets.append({ QShader::SpirvShader, QShaderVersion(100) });
targets.append({ QShader::GlslShader, QShaderVersion(430) });
targets.append({ QShader::MslShader, QShaderVersion(12) });
baker.setGeneratedShaders(targets);
baker.setTessellationMode(QShaderDescription::TrianglesTessellationMode);
QShader s = baker.bake();
QVERIFY(s.isValid());
QVERIFY(s.stage() == QShader::TessellationControlStage);
QVERIFY(baker.errorMessage().isEmpty());
QCOMPARE(s.availableShaders().size(), 3);
QShaderDescription desc = s.description();
// builtins are expected to be stored sorted by type
QCOMPARE(desc.inputBuiltinVariables()[0].type, QShaderDescription::PositionBuiltin);
QCOMPARE(desc.inputBuiltinVariables()[1].type, QShaderDescription::InvocationIdBuiltin);
QCOMPARE(desc.outputBuiltinVariables()[0].type, QShaderDescription::PositionBuiltin);
QCOMPARE(desc.outputBuiltinVariables()[1].type, QShaderDescription::TessLevelOuterBuiltin);
QCOMPARE(desc.outputBuiltinVariables()[2].type, QShaderDescription::TessLevelInnerBuiltin);
QCOMPARE(desc.tessellationOutputVertexCount(), uint(3));
QCOMPARE(desc.outputVariables().size(), 3);
for (const QShaderDescription::InOutVariable &v : desc.outputVariables()) {
switch (v.location) {
case 0:
QCOMPARE(v.name, QByteArrayLiteral("outColor"));
QCOMPARE(v.type, QShaderDescription::Vec3);
QCOMPARE(v.perPatch, false);
break;
case 1:
QCOMPARE(v.name, QByteArrayLiteral("stuff"));
QCOMPARE(v.type, QShaderDescription::Vec3);
QCOMPARE(v.perPatch, true);
break;
case 2:
QCOMPARE(v.name, QByteArrayLiteral("more_stuff"));
QCOMPARE(v.type, QShaderDescription::Float);
QCOMPARE(v.perPatch, true);
break;
default:
QFAIL(qPrintable(QStringLiteral("Bad location: %1").arg(v.location)));
break;
}
}
QVERIFY(s.shader(QShaderKey(QShader::MslShader, QShaderVersion(12))).shader().contains(QByteArrayLiteral("MTLTriangleTessellationFactorsHalf")));
QCOMPARE(s.nativeShaderInfo(QShaderKey(QShader::MslShader, QShaderVersion(12))).extraBufferBindings.size(), 5);
baker.setSourceFileName(QLatin1String(":/data/tess.tese"));
baker.setGeneratedShaders(targets);
baker.setTessellationOutputVertexCount(3);
s = baker.bake();
QVERIFY(s.isValid());
QVERIFY(s.stage() == QShader::TessellationEvaluationStage);
QVERIFY(baker.errorMessage().isEmpty());
QCOMPARE(s.availableShaders().size(), 3);
desc = s.description();
QCOMPARE(desc.inputBuiltinVariables()[0].type, QShaderDescription::PositionBuiltin);
QCOMPARE(desc.inputBuiltinVariables()[1].type, QShaderDescription::TessLevelOuterBuiltin);
QCOMPARE(desc.inputBuiltinVariables()[2].type, QShaderDescription::TessLevelInnerBuiltin);
QCOMPARE(desc.inputBuiltinVariables()[3].type, QShaderDescription::TessCoordBuiltin);
QCOMPARE(desc.tessellationMode(), QShaderDescription::TrianglesTessellationMode);
QCOMPARE(desc.tessellationWindingOrder(), QShaderDescription::CcwTessellationWindingOrder);
QCOMPARE(desc.tessellationPartitioning(), QShaderDescription::FractionalOddTessellationPartitioning);
QCOMPARE(desc.inputVariables().size(), 3);
for (const QShaderDescription::InOutVariable &v : desc.inputVariables()) {
switch (v.location) {
case 0:
QCOMPARE(v.name, QByteArrayLiteral("inColor"));
QCOMPARE(v.type, QShaderDescription::Vec3);
QCOMPARE(v.perPatch, false);
break;
case 1:
QCOMPARE(v.name, QByteArrayLiteral("stuff"));
QCOMPARE(v.type, QShaderDescription::Vec3);
QCOMPARE(v.perPatch, true);
break;
case 2:
QCOMPARE(v.name, QByteArrayLiteral("more_stuff"));
QCOMPARE(v.type, QShaderDescription::Float);
QCOMPARE(v.perPatch, true);
break;
default:
QFAIL(qPrintable(QStringLiteral("Bad location: %1").arg(v.location)));
break;
}
}
// the uniform buffer binding -> Metal buffer index mapping
QCOMPARE(s.nativeResourceBindingMap(QShaderKey(QShader::MslShader, QShaderVersion(12))).size(), 1);
QCOMPARE(s.nativeResourceBindingMap(QShaderKey(QShader::MslShader, QShaderVersion(12))).value(0), std::make_pair(0, -1));
QVERIFY(s.shader(QShaderKey(QShader::MslShader, QShaderVersion(12))).shader().contains(QByteArrayLiteral("[[ patch(triangle, 3) ]]")));
}
void tst_QShaderBaker::tessellationCompileWithChangedTessArgs()
{
QShaderBaker baker;
baker.setSourceFileName(QLatin1String(":/data/tess.tesc"));
baker.setGeneratedShaderVariants({ QShader::StandardShader });
QList<QShaderBaker::GeneratedShader> targets;
targets.append({ QShader::SpirvShader, QShaderVersion(100) });
targets.append({ QShader::MslShader, QShaderVersion(12) });
baker.setGeneratedShaders(targets);
// not what the .tese would say but let's pretend
baker.setTessellationMode(QShaderDescription::QuadTessellationMode);
QShader s = baker.bake();
QVERIFY(s.isValid());
QVERIFY(baker.errorMessage().isEmpty());
QVERIFY(s.shader(QShaderKey(QShader::MslShader, QShaderVersion(12))).shader().contains(QByteArrayLiteral("MTLQuadTessellationFactorsHalf")));
baker.setSourceFileName(QLatin1String(":/data/tess.tese"));
baker.setGeneratedShaders(targets);
baker.setTessellationOutputVertexCount(13);
s = baker.bake();
QVERIFY(s.isValid());
QVERIFY(s.stage() == QShader::TessellationEvaluationStage);
QVERIFY(baker.errorMessage().isEmpty());
QVERIFY(s.shader(QShaderKey(QShader::MslShader, QShaderVersion(12))).shader().contains(QByteArrayLiteral("[[ patch(triangle, 13) ]]")));
}
void tst_QShaderBaker::dontBreakOnTranslationError()
{
QShaderBaker baker;
baker.setSourceFileName(QLatin1String(":/data/texturesize.frag"));
baker.setGeneratedShaderVariants({ QShader::StandardShader });
QVector<QShaderBaker::GeneratedShader> targets;
targets.append({ QShader::SpirvShader, QShaderVersion(100) });
targets.append({ QShader::GlslShader, QShaderVersion(300, QShaderVersion::GlslEs) });
targets.append({ QShader::GlslShader, QShaderVersion(120) });
targets.append({ QShader::HlslShader, QShaderVersion(50) });
targets.append({ QShader::MslShader, QShaderVersion(12) });
baker.setGeneratedShaders(targets);
QShader s = baker.bake();
QVERIFY(s.isValid());
QCOMPARE(s.availableShaders().size(), 5);
QVERIFY(baker.errorMessage().isEmpty());
// now add a problematic target (no textureSize() in GLSL ES 100)
targets.append({ QShader::GlslShader, QShaderVersion(100, QShaderVersion::GlslEs) });
baker.setGeneratedShaders(targets);
QVERIFY(!baker.bake().isValid());
QVERIFY(!baker.errorMessage().isEmpty());
// now make it succeed by skipping the failing targets
baker.setBreakOnShaderTranslationError(false);
s = baker.bake();
QVERIFY(s.isValid());
// somewhat undocumented but the error message still contains the
// SPIRV-Cross error message about GLSL ES 100 even though the
// baking succeeded
QVERIFY(!baker.errorMessage().isEmpty());
QCOMPARE(s.availableShaders().size(), 5);
}
void tst_QShaderBaker::storageImageFlags()
{
QShaderBaker baker;
baker.setSourceFileName(QLatin1String(":/data/imageloadstore.comp"));
baker.setGeneratedShaderVariants({ QShader::StandardShader });
QVector<QShaderBaker::GeneratedShader> targets;
targets.append({ QShader::SpirvShader, QShaderVersion(100) });
baker.setGeneratedShaders(targets);
QShader s = baker.bake();
QVERIFY(s.isValid());
QCOMPARE(s.description().storageImages().size(), 4);
QCOMPARE(s.description().storageImages().at(0).binding, 0);
QCOMPARE(s.description().storageImages().at(0).imageFlags, QShaderDescription::ImageFlags());
QCOMPARE(s.description().storageImages().at(1).binding, 1);
QCOMPARE(s.description().storageImages().at(1).imageFlags,
QShaderDescription::ImageFlags(QShaderDescription::ImageFlag::WriteOnlyImage));
QCOMPARE(s.description().storageImages().at(2).binding, 2);
QCOMPARE(s.description().storageImages().at(2).imageFlags,
QShaderDescription::ImageFlags(QShaderDescription::ImageFlag::ReadOnlyImage));
QCOMPARE(s.description().storageImages().at(3).binding, 3);
QCOMPARE(s.description().storageImages().at(3).imageFlags,
QShaderDescription::ImageFlags(QShaderDescription::ImageFlag::ReadOnlyImage
| QShaderDescription::ImageFlag::WriteOnlyImage));
}
void tst_QShaderBaker::storageBufferRuntimeArrayStride()
{
QShaderBaker baker;
baker.setSourceFileName(QLatin1String(":/data/storagebuffer.comp"));
baker.setGeneratedShaderVariants({ QShader::StandardShader });
QVector<QShaderBaker::GeneratedShader> targets;
targets.append({ QShader::SpirvShader, QShaderVersion(100) });
baker.setGeneratedShaders(targets);
QShader s = baker.bake();
QVERIFY(s.isValid());
QCOMPARE(s.description().storageBlocks().size(), 8);
QCOMPARE(s.description().storageBlocks().at(0).binding, 0);
QCOMPARE(s.description().storageBlocks().at(0).runtimeArrayStride, 4);
QCOMPARE(s.description().storageBlocks().at(1).binding, 1);
QCOMPARE(s.description().storageBlocks().at(1).runtimeArrayStride, 16);
QCOMPARE(s.description().storageBlocks().at(2).binding, 2);
QCOMPARE(s.description().storageBlocks().at(2).runtimeArrayStride, 8);
QCOMPARE(s.description().storageBlocks().at(3).binding, 3);
QCOMPARE(s.description().storageBlocks().at(3).runtimeArrayStride, 16);
QCOMPARE(s.description().storageBlocks().at(4).binding, 4);
QCOMPARE(s.description().storageBlocks().at(4).runtimeArrayStride, 16);
QCOMPARE(s.description().storageBlocks().at(5).binding, 5);
QCOMPARE(s.description().storageBlocks().at(5).runtimeArrayStride, 16);
QCOMPARE(s.description().storageBlocks().at(6).binding, 6);
QCOMPARE(s.description().storageBlocks().at(6).runtimeArrayStride, 16);
QCOMPARE(s.description().storageBlocks().at(7).binding, 7);
QCOMPARE(s.description().storageBlocks().at(7).runtimeArrayStride, 16);
}
void tst_QShaderBaker::storageBufferQualifiers()
{
QShaderBaker baker;
baker.setSourceFileName(QLatin1String(":/data/storagebuffer.comp"));
baker.setGeneratedShaderVariants({ QShader::StandardShader });
QVector<QShaderBaker::GeneratedShader> targets;
targets.append({ QShader::SpirvShader, QShaderVersion(100) });
baker.setGeneratedShaders(targets);
QShader s = baker.bake();
QVERIFY(s.isValid());
// GLSL 4.60 Specification - Section 4.10 Memory Qualifiers:
// Since the external source reading or writing a volatile variable may be another shader
// invocation, variables declared as volatile are automatically treated as coherent.
QCOMPARE(s.description().storageBlocks().size(), 8);
QCOMPARE(s.description().storageBlocks().at(0).binding, 0);
QCOMPARE(s.description().storageBlocks().at(0).qualifierFlags,
QShaderDescription::QualifierFlags());
QCOMPARE(s.description().storageBlocks().at(1).binding, 1);
QCOMPARE(s.description().storageBlocks().at(1).qualifierFlags,
QShaderDescription::QualifierFlags(QShaderDescription::QualifierCoherent));
QCOMPARE(s.description().storageBlocks().at(2).binding, 2);
QCOMPARE(s.description().storageBlocks().at(2).qualifierFlags,
QShaderDescription::QualifierFlags(QShaderDescription::QualifierWriteOnly
| QShaderDescription::QualifierVolatile
| QShaderDescription::QualifierCoherent));
QCOMPARE(s.description().storageBlocks().at(3).binding, 3);
QCOMPARE(s.description().storageBlocks().at(3).qualifierFlags,
QShaderDescription::QualifierFlags(QShaderDescription::QualifierWriteOnly
| QShaderDescription::QualifierRestrict));
QCOMPARE(s.description().storageBlocks().at(4).binding, 4);
QCOMPARE(s.description().storageBlocks().at(4).qualifierFlags,
QShaderDescription::QualifierFlags(QShaderDescription::QualifierReadOnly));
QCOMPARE(s.description().storageBlocks().at(5).binding, 5);
QCOMPARE(s.description().storageBlocks().at(5).qualifierFlags,
QShaderDescription::QualifierFlags(QShaderDescription::QualifierReadOnly
| QShaderDescription::QualifierCoherent));
QCOMPARE(s.description().storageBlocks().at(6).binding, 6);
QCOMPARE(s.description().storageBlocks().at(6).qualifierFlags,
QShaderDescription::QualifierFlags(QShaderDescription::QualifierWriteOnly
| QShaderDescription::QualifierReadOnly
| QShaderDescription::QualifierVolatile
| QShaderDescription::QualifierCoherent));
QCOMPARE(s.description().storageBlocks().at(7).binding, 7);
QCOMPARE(s.description().storageBlocks().at(7).qualifierFlags,
QShaderDescription::QualifierFlags(QShaderDescription::QualifierWriteOnly
| QShaderDescription::QualifierReadOnly
| QShaderDescription::QualifierRestrict));
}
void tst_QShaderBaker::multiview()
{
QShaderBaker baker;
baker.setSourceFileName(QLatin1String(":/data/multiview_simple.vert"));
baker.setGeneratedShaderVariants({ QShader::StandardShader });
QVector<QShaderBaker::GeneratedShader> targets;
// SPV_KHR_multiview
targets.append({ QShader::SpirvShader, QShaderVersion(100) });
// SV_ViewID is in shader model 6.1
targets.append({ QShader::HlslShader, QShaderVersion(61) });
// GL_OVR_multiview(2) is written for OpenGL 3.0 / OpenGL ES 3.0 and newer, just pick
// some GLSL versions accordingly for the test
targets.append({ QShader::GlslShader, QShaderVersion(150) });
targets.append({ QShader::GlslShader, QShaderVersion(300, QShaderVersion::GlslEs) });
baker.setGeneratedShaders(targets);
// should fail due to GLSL
QShader s = baker.bake();
QVERIFY(!s.isValid());
QVERIFY(!baker.errorMessage().isEmpty());
qDebug() << baker.errorMessage();
// now provide num_views and it should succeed
baker.setMultiViewCount(2);
s = baker.bake();
QVERIFY(s.isValid());
QVERIFY(baker.errorMessage().isEmpty());
}
void tst_QShaderBaker::mediump()
{
QShaderBaker baker;
baker.setSourceFileName(QLatin1String(":/data/color.vert"));
baker.setGeneratedShaderVariants({ QShader::StandardShader });
QList<QShaderBaker::GeneratedShader> targets;
targets.append({ QShader::SpirvShader, QShaderVersion(100) });
targets.append({ QShader::GlslShader, QShaderVersion(100, QShaderVersion::GlslEs) });
targets.append({ QShader::GlslShader, QShaderVersion(120) });
targets.append({ QShader::HlslShader, QShaderVersion(50) });
targets.append({ QShader::MslShader, QShaderVersion(12) });
baker.setGlslOptions(QShaderBaker::GlslOption::GlslEsFragDefaultFloatPrecisionMedium);
baker.setGeneratedShaders(targets);
QShader s = baker.bake();
QVERIFY(s.isValid());
QVERIFY(baker.errorMessage().isEmpty());
QCOMPARE(s.availableShaders().size(), 5);
QShaderCode shader = s.shader(QShaderKey(QShader::GlslShader, QShaderVersion(100, QShaderVersion::GlslEs)));
QVERIFY(!shader.shader().isEmpty());
QVERIFY(!shader.shader().contains("precision ")); // no effect for vertex shaders
baker.setSourceFileName(QLatin1String(":/data/color.frag"));
s = baker.bake();
QVERIFY(s.isValid());
QVERIFY(baker.errorMessage().isEmpty());
QCOMPARE(s.availableShaders().size(), 5);
shader = s.shader(QShaderKey(QShader::GlslShader, QShaderVersion(100, QShaderVersion::GlslEs)));
QVERIFY(!shader.shader().isEmpty());
QVERIFY(shader.shader().contains("precision mediump float;"));
// back to defaults
baker.setGlslOptions({});
s = baker.bake();
QVERIFY(s.isValid());
QVERIFY(baker.errorMessage().isEmpty());
QCOMPARE(s.availableShaders().size(), 5);
shader = s.shader(QShaderKey(QShader::GlslShader, QShaderVersion(100, QShaderVersion::GlslEs)));
QVERIFY(!shader.shader().isEmpty());
QVERIFY(shader.shader().contains("precision highp float;"));
}
void tst_QShaderBaker::geometryShader()
{
QShaderBaker baker;
baker.setSourceFileName(QLatin1String(":/data/voxelize.geom"));
baker.setGeneratedShaderVariants({ QShader::StandardShader });
QList<QShaderBaker::GeneratedShader> targets;
targets.append({ QShader::SpirvShader, QShaderVersion(100) });
targets.append({ QShader::GlslShader, QShaderVersion(410) });
targets.append({ QShader::GlslShader, QShaderVersion(320, QShaderVersion::GlslEs) });
targets.append({ QShader::HlslShader, QShaderVersion(50) });
baker.setGeneratedShaders(targets);
QShader s = baker.bake();
QVERIFY(s.isValid());
QVERIFY(s.stage() == QShader::GeometryStage);
QVERIFY(baker.errorMessage().isEmpty());
QCOMPARE(s.availableShaders().size(), 4);
}
#include <tst_qshaderbaker.moc>
QTEST_MAIN(tst_QShaderBaker)