Revision control
++ // Copyright (C) 2020 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++
++ #include <QtTest>
++
++ #include <QtCore/qfile.h>
++ #include <QtCore/qbytearray.h>
++
++ #include <QtQuick3D/private/qquick3dprincipledmaterial_p.h>
++ #include <QtQuick3D/private/qquick3ddefaultmaterial_p.h>
++ #include <QtQuick3D/private/qquick3dcustommaterial_p.h>
++ #include <QtQuick3D/private/qquick3deffect_p.h>
++ #include <QtQuick3D/private/qquick3dmodel_p.h>
++ #include <QtQuick3D/private/qquick3dinstancing_p.h>
++
++ #include <parser.h>
++
++ #include <QtGui/qquaternion.h>
++
++ 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<QQuick3DPrincipledMaterial *>(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<QQuick3DDefaultMaterial *>(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<QQuick3DCustomMaterial *>(sceneData.materials.at(0));
++ QCOMPARE(mat->shadingMode(), QQuick3DCustomMaterial::ShadingMode::Unshaded);
++ }
++
++ void Shadergen::tst_customMaterialUniforms_data()
++ {
++ QTest::addColumn<QMetaType>("type");
++ QTest::addColumn<QString>("name");
++ QTest::addColumn<QVariant>("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<QQuick3DTexture *>()) << "uTex"
++ << QVariant::fromValue((QQuick3DTexture *)nullptr);
++ QTest::newRow("TextureInput") << QMetaType(qMetaTypeId<QQuick3DShaderUtilsTextureInput *>()) << "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<QQuick3DCustomMaterial *>(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<QQuick3DTexture *>()) ||
++ (prop.metaType().id() == qMetaTypeId<QQuick3DShaderUtilsTextureInput *>());
++ if (!ok) {
++ qDebug() << "Texture: " << qMetaTypeId<QQuick3DTexture *>();
++ qDebug() << "TextureInput: " << qMetaTypeId<QQuick3DShaderUtilsTextureInput *>();
++ 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<QMetaType>("type");
++ QTest::addColumn<QString>("name");
++ QTest::addColumn<QVariant>("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<QQuick3DShaderUtilsTextureInput *>()) << "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<QQuick3DEffect *>(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<QQuick3DShaderUtilsTextureInput *>());
++ if (!ok) {
++ qDebug() << "TextureInput: " << qMetaTypeId<QQuick3DShaderUtilsTextureInput *>();
++ qDebug() << prop.metaType().id();
++ }
++ QVERIFY(ok);
++ } else {
++ QCOMPARE(prop, value);
++ }
++ }
++
++ void Shadergen::tst_componentResolving()
++ {
++ QVector<QString> 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<QString> 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"