| /**************************************************************************** |
| ** |
| ** Copyright (C) 2015 Klaralvdalens Datakonsult AB (KDAB). |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the Qt3D module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include <QtTest/QTest> |
| #include <Qt3DCore/private/qnode_p.h> |
| #include <Qt3DCore/private/qscene_p.h> |
| #include <Qt3DRender/private/qrenderstate_p.h> |
| #include <Qt3DCore/private/qnodecreatedchangegenerator_p.h> |
| |
| #include <Qt3DRender/QEffect> |
| #include <Qt3DRender/QMaterial> |
| #include <Qt3DRender/QParameter> |
| #include <Qt3DRender/QTechnique> |
| #include <Qt3DRender/QRenderPass> |
| #include <Qt3DExtras/QPhongMaterial> |
| #include <Qt3DExtras/QDiffuseMapMaterial> |
| #include <Qt3DExtras/QPerVertexColorMaterial> |
| #include <Qt3DExtras/QNormalDiffuseMapMaterial> |
| #include <Qt3DExtras/QDiffuseSpecularMapMaterial> |
| #include <Qt3DExtras/QNormalDiffuseMapAlphaMaterial> |
| #include <Qt3DExtras/QNormalDiffuseSpecularMapMaterial> |
| |
| #include <Qt3DRender/private/qmaterial_p.h> |
| |
| #include "testpostmanarbiter.h" |
| |
| class TestMaterial : public Qt3DRender::QMaterial |
| { |
| public: |
| explicit TestMaterial(Qt3DCore::QNode *parent = 0) |
| : Qt3DRender::QMaterial(parent) |
| , m_effect(new Qt3DRender::QEffect(this)) |
| , m_technique(new Qt3DRender::QTechnique(m_effect)) |
| , m_renderPass(new Qt3DRender::QRenderPass(m_technique)) |
| , m_shaderProgram(new Qt3DRender::QShaderProgram(m_renderPass)) |
| { |
| m_renderPass->setShaderProgram(m_shaderProgram); |
| m_technique->addRenderPass(m_renderPass); |
| m_effect->addTechnique(m_technique); |
| setEffect(m_effect); |
| } |
| |
| Qt3DRender::QEffect *m_effect; |
| Qt3DRender::QTechnique *m_technique; |
| Qt3DRender::QRenderPass *m_renderPass; |
| Qt3DRender::QShaderProgram *m_shaderProgram; |
| }; |
| |
| class tst_QMaterial : public QObject |
| { |
| Q_OBJECT |
| public: |
| tst_QMaterial() |
| : QObject() |
| { |
| qRegisterMetaType<Qt3DCore::QNode*>(); |
| qRegisterMetaType<Qt3DRender::QEffect*>("Qt3DRender::QEffect*"); |
| } |
| |
| private: |
| |
| void compareEffects(const Qt3DRender::QEffect *original, const Qt3DRender::QEffect *clone) |
| { |
| bool isEffectNull = (original == nullptr); |
| if (isEffectNull) { |
| QVERIFY(!clone); |
| } else { |
| QVERIFY(clone); |
| QCOMPARE(original->id(), clone->id()); |
| |
| compareParameters(original->parameters(), clone->parameters()); |
| |
| const int techniqueCounts = original->techniques().size(); |
| QCOMPARE(techniqueCounts, clone->techniques().size()); |
| |
| for (int i = 0; i < techniqueCounts; ++i) |
| compareTechniques(original->techniques().at(i), clone->techniques().at(i)); |
| } |
| } |
| |
| void compareTechniques(const Qt3DRender::QTechnique *original, const Qt3DRender::QTechnique *clone) |
| { |
| QCOMPARE(original->id(), clone->id()); |
| |
| compareParameters(original->parameters(), clone->parameters()); |
| |
| const int passesCount = original->renderPasses().size(); |
| QCOMPARE(passesCount, clone->renderPasses().size()); |
| |
| for (int i = 0; i < passesCount; ++i) |
| compareRenderPasses(original->renderPasses().at(i), clone->renderPasses().at(i)); |
| } |
| |
| void compareRenderPasses(const Qt3DRender::QRenderPass *original, const Qt3DRender::QRenderPass *clone) |
| { |
| QCOMPARE(original->id(), clone->id()); |
| |
| compareParameters(original->parameters(), clone->parameters()); |
| compareRenderStates(original->renderStates(), clone->renderStates()); |
| compareFilterKeys(original->filterKeys(), clone->filterKeys()); |
| compareShaderPrograms(original->shaderProgram(), clone->shaderProgram()); |
| } |
| |
| void compareParameters(const Qt3DRender::ParameterList &original, const Qt3DRender::ParameterList &clone) |
| { |
| QCOMPARE(original.size(), clone.size()); |
| const int parametersCount = original.size(); |
| for (int i = 0; i < parametersCount; ++i) { |
| const Qt3DRender::QParameter *originParam = original.at(i); |
| const Qt3DRender::QParameter *cloneParam = clone.at(i); |
| QCOMPARE(originParam->id(), cloneParam->id()); |
| QCOMPARE(cloneParam->name(), originParam->name()); |
| QCOMPARE(cloneParam->value(), originParam->value()); |
| } |
| } |
| |
| void compareFilterKeys(const QVector<Qt3DRender::QFilterKey *> &original, const QVector<Qt3DRender::QFilterKey *> &clone) |
| { |
| const int annotationsCount = original.size(); |
| QCOMPARE(annotationsCount, clone.size()); |
| |
| for (int i = 0; i < annotationsCount; ++i) { |
| const Qt3DRender::QFilterKey *origAnnotation = original.at(i); |
| const Qt3DRender::QFilterKey *cloneAnnotation = clone.at(i); |
| QCOMPARE(origAnnotation->id(), cloneAnnotation->id()); |
| QCOMPARE(origAnnotation->name(), cloneAnnotation->name()); |
| QCOMPARE(origAnnotation->value(), cloneAnnotation->value()); |
| } |
| } |
| |
| void compareRenderStates(const QVector<Qt3DRender::QRenderState *> &original, const QVector<Qt3DRender::QRenderState *> &clone) |
| { |
| const int renderStatesCount = original.size(); |
| QCOMPARE(renderStatesCount, clone.size()); |
| |
| for (int i = 0; i < renderStatesCount; ++i) { |
| Qt3DRender::QRenderState *originState = original.at(i); |
| Qt3DRender::QRenderState *cloneState = clone.at(i); |
| QCOMPARE(originState->id(), originState->id()); |
| QVERIFY(Qt3DRender::QRenderStatePrivate::get(originState)->m_type == Qt3DRender::QRenderStatePrivate::get(cloneState)->m_type); |
| } |
| } |
| |
| void compareShaderPrograms(const Qt3DRender::QShaderProgram *original, const Qt3DRender::QShaderProgram *clone) |
| { |
| bool isOriginalNull = (original == nullptr); |
| if (isOriginalNull) { |
| QVERIFY(!clone); |
| } else { |
| QVERIFY(clone); |
| QCOMPARE(original->id(), clone->id()); |
| QVERIFY(original->vertexShaderCode() == clone->vertexShaderCode()); |
| QVERIFY(original->fragmentShaderCode() == clone->fragmentShaderCode()); |
| QVERIFY(original->geometryShaderCode() == clone->geometryShaderCode()); |
| QVERIFY(original->computeShaderCode() == clone->computeShaderCode()); |
| QVERIFY(original->tessellationControlShaderCode() == clone->tessellationControlShaderCode()); |
| QVERIFY(original->tessellationEvaluationShaderCode() == clone->tessellationEvaluationShaderCode()); |
| } |
| } |
| |
| private Q_SLOTS: |
| |
| void checkCloning_data() |
| { |
| QTest::addColumn<Qt3DRender::QMaterial *>("material"); |
| |
| Qt3DRender::QMaterial *material = new Qt3DRender::QMaterial(); |
| QTest::newRow("empty material") << material; |
| material = new TestMaterial(); |
| QTest::newRow("test material") << material; |
| material = new Qt3DExtras::QPhongMaterial(); |
| QTest::newRow("QPhongMaterial") << material; |
| material = new Qt3DExtras::QDiffuseMapMaterial(); |
| QTest::newRow("QDiffuseMapMaterial") << material; |
| material = new Qt3DExtras::QDiffuseSpecularMapMaterial(); |
| QTest::newRow("QDiffuseMapSpecularMaterial") << material; |
| material = new Qt3DExtras::QPerVertexColorMaterial(); |
| QTest::newRow("QPerVertexColorMaterial") << material; |
| material = new Qt3DExtras::QNormalDiffuseMapMaterial(); |
| QTest::newRow("QNormalDiffuseMapMaterial") << material; |
| material = new Qt3DExtras::QNormalDiffuseMapAlphaMaterial(); |
| QTest::newRow("QNormalDiffuseMapAlphaMaterial") << material; |
| material = new Qt3DExtras::QNormalDiffuseSpecularMapMaterial(); |
| QTest::newRow("QNormalDiffuseSpecularMapMaterial") << material; |
| } |
| |
| void checkCloning() |
| { |
| // GIVEN |
| QFETCH(Qt3DRender::QMaterial *, material); |
| |
| // WHEN |
| Qt3DCore::QNodeCreatedChangeGenerator creationChangeGenerator(material); |
| QVector<Qt3DCore::QNodeCreatedChangeBasePtr> creationChanges = creationChangeGenerator.creationChanges(); |
| |
| // THEN |
| QVERIFY(creationChanges.size() >= 1); |
| |
| const Qt3DCore::QNodeCreatedChangePtr<Qt3DRender::QMaterialData> creationChangeData = |
| qSharedPointerCast<Qt3DCore::QNodeCreatedChange<Qt3DRender::QMaterialData>>(creationChanges.first()); |
| const Qt3DRender::QMaterialData &cloneData = creationChangeData->data; |
| |
| // THEN |
| QCOMPARE(material->id(), creationChangeData->subjectId()); |
| QCOMPARE(material->isEnabled(), creationChangeData->isNodeEnabled()); |
| QCOMPARE(material->metaObject(), creationChangeData->metaObject()); |
| QCOMPARE(material->effect() ? material->effect()->id() : Qt3DCore::QNodeId(), cloneData.effectId); |
| QCOMPARE(material->parameters().size(), cloneData.parameterIds.size()); |
| |
| for (int i = 0, m = material->parameters().size(); i < m; ++i) |
| QCOMPARE(material->parameters().at(i)->id(), cloneData.parameterIds.at(i)); |
| |
| // TO DO: Add unit tests for parameter and effect that do check this |
| // compareParameters(material->parameters(), clone->parameters()); |
| // compareEffects(material->effect(), clone->effect()); |
| } |
| |
| void checkEffectUpdate() |
| { |
| // GIVEN |
| TestArbiter arbiter; |
| QScopedPointer<Qt3DRender::QMaterial> material(new Qt3DRender::QMaterial()); |
| arbiter.setArbiterOnNode(material.data()); |
| |
| // WHEN |
| Qt3DRender::QEffect effect; |
| material->setEffect(&effect); |
| |
| // THEN |
| QCOMPARE(arbiter.dirtyNodes.size(), 1); |
| QCOMPARE(arbiter.dirtyNodes.front(), material.data()); |
| |
| arbiter.dirtyNodes.clear(); |
| |
| // GIVEN |
| TestArbiter arbiter2; |
| QScopedPointer<TestMaterial> material2(new TestMaterial()); |
| arbiter2.setArbiterOnNode(material2.data()); |
| |
| // WHEN |
| material2->setEffect(&effect); |
| |
| // THEN |
| QCOMPARE(arbiter2.dirtyNodes.size(), 1); |
| QCOMPARE(arbiter2.dirtyNodes.front(), material2.data()); |
| |
| arbiter2.dirtyNodes.clear(); |
| } |
| |
| void checkDynamicParametersAddedUpdates() |
| { |
| // GIVEN |
| TestArbiter arbiter; |
| TestMaterial *material = new TestMaterial(); |
| arbiter.setArbiterOnNode(material); |
| |
| QCoreApplication::processEvents(); |
| // Clear events trigger by child generation of TestMnterial |
| arbiter.events.clear(); |
| |
| // WHEN (add parameter to material) |
| Qt3DRender::QParameter *param = new Qt3DRender::QParameter("testParamMaterial", QVariant::fromValue(383.0f)); |
| material->addParameter(param); |
| QCoreApplication::processEvents(); |
| |
| // THEN |
| QCOMPARE(param->parent(), material); |
| |
| // THEN |
| QCOMPARE(arbiter.events.size(), 0); |
| QCOMPARE(arbiter.dirtyNodes.size(), 1); |
| QVERIFY(material->parameters().contains(param)); |
| |
| // WHEN (add parameter to effect) |
| param = new Qt3DRender::QParameter("testParamEffect", QVariant::fromValue(383.0f)); |
| material->effect()->addParameter(param); |
| QCoreApplication::processEvents(); |
| |
| // THEN |
| QCOMPARE(arbiter.events.size(), 0); |
| QCOMPARE(arbiter.dirtyNodes.size(), 2); |
| QVERIFY(material->effect()->parameters().contains(param)); |
| |
| // WHEN (add parameter to technique) |
| param = new Qt3DRender::QParameter("testParamTechnique", QVariant::fromValue(383.0f)); |
| material->m_technique->addParameter(param); |
| QCoreApplication::processEvents(); |
| |
| // THEN |
| QCOMPARE(arbiter.events.size(), 0); |
| QCOMPARE(arbiter.dirtyNodes.size(), 3); |
| QVERIFY(material->m_technique->parameters().contains(param)); |
| |
| |
| // WHEN (add parameter to renderpass) |
| param = new Qt3DRender::QParameter("testParamRenderPass", QVariant::fromValue(383.0f)); |
| material->m_renderPass->addParameter(param); |
| QCoreApplication::processEvents(); |
| |
| // THEN |
| QCOMPARE(arbiter.events.size(), 0); |
| QCOMPARE(arbiter.dirtyNodes.size(), 4); |
| QVERIFY(material->m_renderPass->parameters().contains(param)); |
| } |
| |
| void checkShaderProgramUpdates() |
| { |
| // GIVEN |
| TestArbiter arbiter; |
| TestMaterial *material = new TestMaterial(); |
| arbiter.setArbiterOnNode(material); |
| |
| // WHEN |
| const QByteArray vertexCode = QByteArrayLiteral("new vertex shader code"); |
| material->m_shaderProgram->setVertexShaderCode(vertexCode); |
| |
| // THEN |
| QCOMPARE(arbiter.dirtyNodes.size(), 1); |
| QCOMPARE(arbiter.dirtyNodes.front(), material->m_shaderProgram); |
| |
| arbiter.dirtyNodes.clear(); |
| |
| // WHEN |
| const QByteArray fragmentCode = QByteArrayLiteral("new fragment shader code"); |
| material->m_shaderProgram->setFragmentShaderCode(fragmentCode); |
| |
| // THEN |
| QCOMPARE(arbiter.dirtyNodes.size(), 1); |
| QCOMPARE(arbiter.dirtyNodes.front(), material->m_shaderProgram); |
| |
| arbiter.dirtyNodes.clear(); |
| // WHEN |
| const QByteArray geometryCode = QByteArrayLiteral("new geometry shader code"); |
| material->m_shaderProgram->setGeometryShaderCode(geometryCode); |
| |
| // THEN |
| QCOMPARE(arbiter.dirtyNodes.size(), 1); |
| QCOMPARE(arbiter.dirtyNodes.front(), material->m_shaderProgram); |
| |
| arbiter.dirtyNodes.clear(); |
| |
| // WHEN |
| const QByteArray computeCode = QByteArrayLiteral("new compute shader code"); |
| material->m_shaderProgram->setComputeShaderCode(computeCode); |
| |
| // THEN |
| QCOMPARE(arbiter.dirtyNodes.size(), 1); |
| QCOMPARE(arbiter.dirtyNodes.front(), material->m_shaderProgram); |
| |
| arbiter.dirtyNodes.clear(); |
| |
| // WHEN |
| const QByteArray tesselControlCode = QByteArrayLiteral("new tessellation control shader code"); |
| material->m_shaderProgram->setTessellationControlShaderCode(tesselControlCode); |
| |
| // THEN |
| QCOMPARE(arbiter.dirtyNodes.size(), 1); |
| QCOMPARE(arbiter.dirtyNodes.front(), material->m_shaderProgram); |
| |
| arbiter.dirtyNodes.clear(); |
| |
| // WHEN |
| const QByteArray tesselEvalCode = QByteArrayLiteral("new tessellation eval shader code"); |
| material->m_shaderProgram->setTessellationEvaluationShaderCode(tesselEvalCode); |
| |
| // THEN |
| QCOMPARE(arbiter.dirtyNodes.size(), 1); |
| QCOMPARE(arbiter.dirtyNodes.front(), material->m_shaderProgram); |
| |
| arbiter.dirtyNodes.clear(); |
| } |
| |
| void checkEffectBookkeeping() |
| { |
| // GIVEN |
| QScopedPointer<Qt3DRender::QMaterial> material(new Qt3DRender::QMaterial); |
| { |
| // WHEN |
| Qt3DRender::QEffect effect; |
| material->setEffect(&effect); |
| |
| // THEN |
| QCOMPARE(effect.parent(), material.data()); |
| QCOMPARE(material->effect(), &effect); |
| } |
| // THEN (Should not crash and effect be unset) |
| QVERIFY(material->effect() == nullptr); |
| |
| { |
| // WHEN |
| Qt3DRender::QMaterial someOtherMaterial; |
| QScopedPointer<Qt3DRender::QEffect> effect(new Qt3DRender::QEffect(&someOtherMaterial)); |
| material->setEffect(effect.data()); |
| |
| // THEN |
| QCOMPARE(effect->parent(), &someOtherMaterial); |
| QCOMPARE(material->effect(), effect.data()); |
| |
| // WHEN |
| material.reset(); |
| effect.reset(); |
| |
| // THEN Should not crash when the effect is destroyed (tests for failed removal of destruction helper) |
| } |
| } |
| |
| void checkParametersBookkeeping() |
| { |
| // GIVEN |
| QScopedPointer<Qt3DRender::QMaterial> material(new Qt3DRender::QMaterial); |
| { |
| // WHEN |
| Qt3DRender::QParameter param; |
| material->addParameter(¶m); |
| |
| // THEN |
| QCOMPARE(param.parent(), material.data()); |
| QCOMPARE(material->parameters().size(), 1); |
| } |
| // THEN (Should not crash and parameter be unset) |
| QVERIFY(material->parameters().empty()); |
| |
| { |
| // WHEN |
| Qt3DRender::QMaterial someOtherMaterial; |
| QScopedPointer<Qt3DRender::QParameter> param(new Qt3DRender::QParameter(&someOtherMaterial)); |
| material->addParameter(param.data()); |
| |
| // THEN |
| QCOMPARE(param->parent(), &someOtherMaterial); |
| QCOMPARE(material->parameters().size(), 1); |
| |
| // WHEN |
| material.reset(); |
| param.reset(); |
| |
| // THEN Should not crash when the parameter is destroyed (tests for failed removal of destruction helper) |
| } |
| } |
| }; |
| |
| QTEST_MAIN(tst_QMaterial) |
| |
| #include "tst_qmaterial.moc" |