// Copyright (C) 2020 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include #include #include #include #include #include #include #include #include #include class Shadergen : public QObject { Q_OBJECT public: Shadergen() = default; ~Shadergen() = default; private Q_SLOTS: void initTestCase(); void tst_principledMaterialComponent(); void tst_defaultMaterialComponent(); void tst_customMaterialComponent(); void tst_customMaterialUniforms_data(); void tst_customMaterialUniforms(); void tst_effectComponent(); void tst_effectUniforms_data(); void tst_effectUniforms(); void tst_componentResolving(); void tst_instancing(); private: MaterialParser::SceneData lastSceneData; }; void Shadergen::initTestCase() { } void Shadergen::tst_principledMaterialComponent() { QFile file(QLatin1String(":/qml/PrincipledMaterialA.qml")); QVERIFY(file.open(QIODevice::ReadOnly)); QByteArray data = file.readAll(); MaterialParser::SceneData sceneData; MaterialParser::parseQmlData(data, file.fileName(), sceneData); QCOMPARE(sceneData.materials.size(), 1); QQuick3DPrincipledMaterial *mat = qobject_cast(sceneData.materials.at(0)); QVERIFY(qFuzzyCompare(mat->metalness(), 0.1f)); QVERIFY(qFuzzyCompare(mat->roughness(), 0.2f)); QCOMPARE(mat->baseColor(), QColor("green")); } void Shadergen::tst_defaultMaterialComponent() { QFile file(QLatin1String(":/qml/DefaultMaterialA.qml")); QVERIFY(file.open(QIODevice::ReadOnly)); QByteArray data = file.readAll(); MaterialParser::SceneData sceneData; MaterialParser::parseQmlData(data, file.fileName(), sceneData); QCOMPARE(sceneData.materials.size(), 1); QQuick3DDefaultMaterial *mat = qobject_cast(sceneData.materials.at(0)); QVERIFY(qFuzzyCompare(mat->specularAmount(), 0.1f)); QVERIFY(qFuzzyCompare(mat->specularRoughness(), 0.2f)); QCOMPARE(mat->diffuseColor(), QColor("pink")); } void Shadergen::tst_customMaterialComponent() { QFile file(QLatin1String(":/qml/CustomMaterialA.qml")); QVERIFY(file.open(QIODevice::ReadOnly)); QByteArray data = file.readAll(); auto &sceneData = lastSceneData; MaterialParser::parseQmlData(data, file.fileName(), sceneData); QCOMPARE(sceneData.materials.size(), 1); QQuick3DCustomMaterial *mat = qobject_cast(sceneData.materials.at(0)); QCOMPARE(mat->shadingMode(), QQuick3DCustomMaterial::ShadingMode::Unshaded); } void Shadergen::tst_customMaterialUniforms_data() { QTest::addColumn("type"); QTest::addColumn("name"); QTest::addColumn("value"); QTest::newRow("bool0") << QMetaType(QMetaType::Bool) << "uBoolFalse" << QVariant::fromValue(false); QTest::newRow("bool1") << QMetaType(QMetaType::Bool) << "uBoolTrue" << QVariant::fromValue(true); QTest::newRow("int") << QMetaType(QMetaType::Double) /*Not a typo*/ << "uInt" << QVariant::fromValue(int(33)); QTest::newRow("real") << QMetaType(QMetaType::Double) << "uReal" << QVariant::fromValue(qreal(3.3)); QTest::newRow("pointS") << QMetaType(QMetaType::QPointF) << "uPointS" << QVariant::fromValue(QPointF(0, 1)); QTest::newRow("pointF") << QMetaType(QMetaType::QPointF) << "uPointF" << QVariant::fromValue(QPointF(1, 0)); QTest::newRow("sizeS") << QMetaType(QMetaType::QSizeF) << "uSizeS" << QVariant::fromValue(QSizeF(1, 1)); QTest::newRow("sizeF") << QMetaType(QMetaType::QSizeF) << "uSizeF" << QVariant::fromValue(QSizeF(2, 2)); QTest::newRow("rectS") << QMetaType(QMetaType::QRectF) << "uRectS" << QVariant::fromValue(QRectF(0, 1, 100, 101)); QTest::newRow("rectF") << QMetaType(QMetaType::QRectF) << "uRectF" << QVariant::fromValue(QRectF(1, 0, 101, 100)); QTest::newRow("vec2") << QMetaType(QMetaType::QVector2D) << "uVec2" << QVariant::fromValue(QVector2D(1, 2)); QTest::newRow("vec3") << QMetaType(QMetaType::QVector3D) << "uVec3" << QVariant::fromValue(QVector3D(1, 2, 3)); QTest::newRow("vec4") << QMetaType(QMetaType::QVector4D) << "uVec4" << QVariant::fromValue(QVector4D(1, 2, 3, 4)); QTest::newRow("quat") << QMetaType(QMetaType::QQuaternion) << "uQuat" << QVariant::fromValue(QQuaternion(1, 2, 3, 4)); QTest::newRow("m44") << QMetaType(QMetaType::QMatrix4x4) << "uM44" << QVariant::fromValue(QMatrix4x4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)); QTest::newRow("Color1") << QMetaType(QMetaType::QColor) << "uColor1" << QVariant::fromValue(QColor("green")); QTest::newRow("Color2") << QMetaType(QMetaType::QColor) << "uColor2" << QVariant::fromValue(QColor("#ff0000")); QTest::newRow("Texture") << QMetaType(qMetaTypeId()) << "uTex" << QVariant::fromValue((QQuick3DTexture *)nullptr); QTest::newRow("TextureInput") << QMetaType(qMetaTypeId()) << "uTexInput" << QVariant::fromValue((QQuick3DShaderUtilsTextureInput *)nullptr); } void Shadergen::tst_customMaterialUniforms() { // Test that public QML members are added to the object const auto &sceneData = lastSceneData; QCOMPARE(sceneData.materials.size(), 1); QQuick3DCustomMaterial *mat = qobject_cast(sceneData.materials.at(0)); QCOMPARE(mat->shadingMode(), QQuick3DCustomMaterial::ShadingMode::Unshaded); QFETCH(QMetaType, type); QFETCH(QString, name); QFETCH(QVariant, value); const auto prop = mat->property(name.toLatin1().constData()); QVERIFY(prop.isValid()); QCOMPARE(prop.metaType(), type); if (type == QMetaType(QMetaType::Double)) { QVERIFY(qFuzzyCompare(prop.toDouble(), value.toDouble())); } else if (type.id() >= QMetaType::User) { const bool ok = (prop.metaType().id() == qMetaTypeId()) || (prop.metaType().id() == qMetaTypeId()); if (!ok) { qDebug() << "Texture: " << qMetaTypeId(); qDebug() << "TextureInput: " << qMetaTypeId(); qDebug() << prop.metaType().id(); } QVERIFY(ok); } else { QCOMPARE(prop, value); } } void Shadergen::tst_effectComponent() { QFile file(QLatin1String(":/qml/EffectA.qml")); QVERIFY(file.open(QIODevice::ReadOnly)); QByteArray data = file.readAll(); MaterialParser::SceneData sceneData; MaterialParser::parseQmlData(data, file.fileName(), sceneData); QCOMPARE(sceneData.effects.size(), 1); QQuick3DEffect *effect = sceneData.effects.at(0); auto passes = effect->passes(); QCOMPARE(passes.count(&passes), 1); auto pass = passes.at(&passes, 0); QVERIFY(pass); auto shaders = pass->shaders(); { const auto count = shaders.count(&shaders); QCOMPARE(count, 2); // Vertex shader auto shader = shaders.at(&shaders, 0); // Shader url QVERIFY(!shader->shader.isEmpty()); QCOMPARE(shader->stage, QQuick3DShaderUtilsShader::Stage::Vertex); // Fragment shader shader = shaders.at(&shaders, 1); // Shader url QVERIFY(!shader->shader.isEmpty()); QCOMPARE(shader->stage, QQuick3DShaderUtilsShader::Stage::Fragment); } lastSceneData = std::move(sceneData); } void Shadergen::tst_effectUniforms_data() { QTest::addColumn("type"); QTest::addColumn("name"); QTest::addColumn("value"); QTest::newRow("bool0") << QMetaType(QMetaType::Bool) << "uBoolFalse" << QVariant::fromValue(false); QTest::newRow("bool1") << QMetaType(QMetaType::Bool) << "uBoolTrue" << QVariant::fromValue(true); QTest::newRow("int") << QMetaType(QMetaType::Double) /*Not a typo*/ << "uInt" << QVariant::fromValue(int(33)); QTest::newRow("real") << QMetaType(QMetaType::Double) << "uReal" << QVariant::fromValue(qreal(3.3)); QTest::newRow("pointS") << QMetaType(QMetaType::QPointF) << "uPointS" << QVariant::fromValue(QPointF(0, 1)); QTest::newRow("pointF") << QMetaType(QMetaType::QPointF) << "uPointF" << QVariant::fromValue(QPointF(1, 0)); QTest::newRow("sizeS") << QMetaType(QMetaType::QSizeF) << "uSizeS" << QVariant::fromValue(QSizeF(1, 1)); QTest::newRow("sizeF") << QMetaType(QMetaType::QSizeF) << "uSizeF" << QVariant::fromValue(QSizeF(2, 2)); QTest::newRow("rectS") << QMetaType(QMetaType::QRectF) << "uRectS" << QVariant::fromValue(QRectF(0, 1, 100, 101)); QTest::newRow("rectF") << QMetaType(QMetaType::QRectF) << "uRectF" << QVariant::fromValue(QRectF(1, 0, 101, 100)); QTest::newRow("vec2") << QMetaType(QMetaType::QVector2D) << "uVec2" << QVariant::fromValue(QVector2D(1, 2)); QTest::newRow("vec3") << QMetaType(QMetaType::QVector3D) << "uVec3" << QVariant::fromValue(QVector3D(1, 2, 3)); QTest::newRow("vec4") << QMetaType(QMetaType::QVector4D) << "uVec4" << QVariant::fromValue(QVector4D(1, 2, 3, 4)); QTest::newRow("quat") << QMetaType(QMetaType::QQuaternion) << "uQuat" << QVariant::fromValue(QQuaternion(1, 2, 3, 4)); QTest::newRow("m44") << QMetaType(QMetaType::QMatrix4x4) << "uM44" << QVariant::fromValue(QMatrix4x4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)); QTest::newRow("Color1") << QMetaType(QMetaType::QColor) << "uColor1" << QVariant::fromValue(QColor("green")); QTest::newRow("Color2") << QMetaType(QMetaType::QColor) << "uColor2" << QVariant::fromValue(QColor("#ff0000")); QTest::newRow("TextureInput") << QMetaType(qMetaTypeId()) << "uTexInput" << QVariant::fromValue((QQuick3DShaderUtilsTextureInput *)nullptr); } void Shadergen::tst_effectUniforms() { // Test that public QML members are added to the object const auto &sceneData = lastSceneData; QCOMPARE(sceneData.effects.size(), 1); QQuick3DEffect *effect = qobject_cast(sceneData.effects.at(0)); QFETCH(QMetaType, type); QFETCH(QString, name); QFETCH(QVariant, value); const auto prop = effect->property(name.toLatin1().constData()); QVERIFY(prop.isValid()); QCOMPARE(prop.metaType(), type); if (type == QMetaType(QMetaType::Double)) { QVERIFY(qFuzzyCompare(prop.toDouble(), value.toDouble())); } else if (type.id() >= QMetaType::User) { const bool ok = (prop.metaType().id() == qMetaTypeId()); if (!ok) { qDebug() << "TextureInput: " << qMetaTypeId(); qDebug() << prop.metaType().id(); } QVERIFY(ok); } else { QCOMPARE(prop, value); } } void Shadergen::tst_componentResolving() { QVector filePaths { ":/qml/main.qml", ":/qml/ModelA.qml", ":/qml/DefaultMaterialA.qml" }; MaterialParser::SceneData sceneData; MaterialParser::parseQmlFiles(filePaths, QString(), sceneData, false); // main.qml(ModelA, Model, Model, ModelA, ModelA, ModelA, ModelA). Component should not be added as it's used const auto &models = sceneData.models; // The correct number should be 7 here, but we don't track if a component is // actually used so it gets pushed into the same list as the regular instances. QCOMPARE(models.size(), 8); // We skip testing the component here and start at index 1 { // Model component (ModelA) with inherited material (1) const auto &model = models.at(1); auto materials = model->materials(); QCOMPARE(materials.count(&materials), 1); } { // Model with refed component material (DefaultMaterialA) (1) const auto &model = models.at(2); auto materials = model->materials(); QCOMPARE(materials.count(&materials), 1); } { // Model with inline component material (DefaultMaterialA) (1) const auto &model = models.at(3); auto materials = model->materials(); QCOMPARE(materials.count(&materials), 1); } { // Model component (ModelA) overrides inherited material with refed component material (DefaultMaterialA) (1) const auto &model = models.at(4); auto materials = model->materials(); QCOMPARE(materials.count(&materials), 1); } { // Model component (ModelA) overrides inherited material inline (DefaultMaterial) (1) const auto &model = models.at(5); auto materials = model->materials(); QCOMPARE(materials.count(&materials), 1); } { // Model component (ModelA) override inherited material with refed materials (2) const auto &model = models.at(6); auto materials = model->materials(); QCOMPARE(materials.count(&materials), 2); } { // Model component (ModelA) override inherited material with inline materials (2) const auto &model = models.at(7); auto materials = model->materials(); QCOMPARE(materials.count(&materials), 2); } } void Shadergen::tst_instancing() { QVector filePaths { ":/qml/instancing.qml" }; MaterialParser::SceneData sceneData; MaterialParser::parseQmlFiles(filePaths, QString(), sceneData, false); // instancing.qml(Model). const auto &models = sceneData.models; QCOMPARE(models.size(), 1); { const auto &model = models.at(0); auto instancing = model->instancing(); QVERIFY(instancing != nullptr); int count = -1; const auto buffer = instancing->instanceBuffer(&count); (void)buffer; QCOMPARE(count, 2); } } QTEST_APPLESS_MAIN(Shadergen) #include "tst_shadergen.moc"