Revision control

++ // Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB).
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++
++
++ #include <QtTest/QTest>
++
++ #include <Qt3DRender/private/qshadergraph_p.h>
++
++ using namespace Qt3DRender;
++ namespace
++ {
++ QShaderNodePort createPort(QShaderNodePort::Direction portDirection, const QString &portName)
++ {
++ auto port = QShaderNodePort();
++ port.direction = portDirection;
++ port.name = portName;
++ return port;
++ }
++
++ QShaderNode createNode(const QList<QShaderNodePort> &ports, const QStringList &layers = QStringList())
++ {
++ auto node = QShaderNode();
++ node.setUuid(QUuid::createUuid());
++ node.setLayers(layers);
++ for (const auto &port : ports)
++ node.addPort(port);
++ return node;
++ }
++
++ QShaderGraph::Edge createEdge(const QUuid &sourceUuid, const QString &sourceName,
++ const QUuid &targetUuid, const QString &targetName,
++ const QStringList &layers = QStringList())
++ {
++ auto edge = QShaderGraph::Edge();
++ edge.sourceNodeUuid = sourceUuid;
++ edge.sourcePortName = sourceName;
++ edge.targetNodeUuid = targetUuid;
++ edge.targetPortName = targetName;
++ edge.layers = layers;
++ return edge;
++ }
++
++ QShaderGraph::Statement createStatement(const QShaderNode &node,
++ const QList<int> &inputs = QList<int>(),
++ const QList<int> &outputs = QList<int>())
++ {
++ auto statement = QShaderGraph::Statement();
++ statement.node = node;
++ statement.inputs = inputs;
++ statement.outputs = outputs;
++ return statement;
++ }
++
++ void debugStatement(const QString &prefix, const QShaderGraph::Statement &statement)
++ {
++ qDebug() << prefix << statement.inputs << statement.uuid().toString() << statement.outputs;
++ }
++
++ void dumpStatementsIfNeeded(const QList<QShaderGraph::Statement> &statements, const QList<QShaderGraph::Statement> &expected)
++ {
++ if (statements != expected) {
++ for (int i = 0; i < qMax(statements.size(), expected.size()); i++) {
++ qDebug() << "----" << i << "----";
++ if (i < statements.size())
++ debugStatement("A:", statements.at(i));
++ if (i < expected.size())
++ debugStatement("E:", expected.at(i));
++ qDebug() << "-----------";
++ }
++ }
++ }
++ }
++
++ class tst_QShaderGraph : public QObject
++ {
++ Q_OBJECT
++ private slots:
++ void shouldHaveEdgeDefaultState();
++ void shouldTestEdgesEquality_data();
++ void shouldTestEdgesEquality();
++ void shouldManipulateStatementMembers();
++ void shouldTestStatementsEquality_data();
++ void shouldTestStatementsEquality();
++ void shouldFindIndexFromPortNameInStatements_data();
++ void shouldFindIndexFromPortNameInStatements();
++ void shouldManageNodeList();
++ void shouldManageEdgeList();
++ void shouldSerializeGraphForCodeGeneration();
++ void shouldHandleUnboundPortsDuringGraphSerialization();
++ void shouldSurviveCyclesDuringGraphSerialization();
++ void shouldDealWithEdgesJumpingOverLayers();
++ void shouldGenerateDifferentStatementsDependingOnActiveLayers();
++ void shouldDealWithBranchesWithoutOutput();
++ void shouldDiscardEdgesConnectedToDiscardedNodes();
++ };
++
++ void tst_QShaderGraph::shouldHaveEdgeDefaultState()
++ {
++ // GIVEN
++ auto edge = QShaderGraph::Edge();
++
++ // THEN
++ QVERIFY(edge.sourceNodeUuid.isNull());
++ QVERIFY(edge.sourcePortName.isEmpty());
++ QVERIFY(edge.targetNodeUuid.isNull());
++ QVERIFY(edge.targetPortName.isEmpty());
++ }
++
++ void tst_QShaderGraph::shouldTestEdgesEquality_data()
++ {
++ QTest::addColumn<QShaderGraph::Edge>("left");
++ QTest::addColumn<QShaderGraph::Edge>("right");
++ QTest::addColumn<bool>("expected");
++
++ const auto sourceUuid1 = QUuid::createUuid();
++ const auto sourceUuid2 = QUuid::createUuid();
++ const auto targetUuid1 = QUuid::createUuid();
++ const auto targetUuid2 = QUuid::createUuid();
++
++ QTest::newRow("Equals") << createEdge(sourceUuid1, "foo", targetUuid1, "bar")
++ << createEdge(sourceUuid1, "foo", targetUuid1, "bar")
++ << true;
++ QTest::newRow("SourceUuid") << createEdge(sourceUuid1, "foo", targetUuid1, "bar")
++ << createEdge(sourceUuid2, "foo", targetUuid1, "bar")
++ << false;
++ QTest::newRow("SourceName") << createEdge(sourceUuid1, "foo", targetUuid1, "bar")
++ << createEdge(sourceUuid1, "bleh", targetUuid1, "bar")
++ << false;
++ QTest::newRow("TargetUuid") << createEdge(sourceUuid1, "foo", targetUuid1, "bar")
++ << createEdge(sourceUuid1, "foo", targetUuid2, "bar")
++ << false;
++ QTest::newRow("TargetName") << createEdge(sourceUuid1, "foo", targetUuid1, "bar")
++ << createEdge(sourceUuid1, "foo", targetUuid1, "bleh")
++ << false;
++ }
++
++ void tst_QShaderGraph::shouldTestEdgesEquality()
++ {
++ // GIVEN
++ QFETCH(QShaderGraph::Edge, left);
++ QFETCH(QShaderGraph::Edge, right);
++
++ // WHEN
++ const auto equal = (left == right);
++ const auto notEqual = (left != right);
++
++ // THEN
++ QFETCH(bool, expected);
++ QCOMPARE(equal, expected);
++ QCOMPARE(notEqual, !expected);
++ }
++
++ void tst_QShaderGraph::shouldManipulateStatementMembers()
++ {
++ // GIVEN
++ auto statement = QShaderGraph::Statement();
++
++ // THEN (default state)
++ QVERIFY(statement.inputs.isEmpty());
++ QVERIFY(statement.outputs.isEmpty());
++ QVERIFY(statement.node.uuid().isNull());
++ QVERIFY(statement.uuid().isNull());
++
++ // WHEN
++ const auto node = createNode({});
++ statement.node = node;
++
++ // THEN
++ QCOMPARE(statement.uuid(), node.uuid());
++
++ // WHEN
++ statement.node = QShaderNode();
++
++ // THEN
++ QVERIFY(statement.uuid().isNull());
++ }
++
++ void tst_QShaderGraph::shouldTestStatementsEquality_data()
++ {
++ QTest::addColumn<QShaderGraph::Statement>("left");
++ QTest::addColumn<QShaderGraph::Statement>("right");
++ QTest::addColumn<bool>("expected");
++
++ const auto node1 = createNode({});
++ const auto node2 = createNode({});
++
++ QTest::newRow("EqualNodes") << createStatement(node1, {1, 2}, {3, 4})
++ << createStatement(node1, {1, 2}, {3, 4})
++ << true;
++ QTest::newRow("EqualInvalids") << createStatement(QShaderNode(), {1, 2}, {3, 4})
++ << createStatement(QShaderNode(), {1, 2}, {3, 4})
++ << true;
++ QTest::newRow("Nodes") << createStatement(node1, {1, 2}, {3, 4})
++ << createStatement(node2, {1, 2}, {3, 4})
++ << false;
++ QTest::newRow("Inputs") << createStatement(node1, {1, 2}, {3, 4})
++ << createStatement(node1, {1, 2, 0}, {3, 4})
++ << false;
++ QTest::newRow("Outputs") << createStatement(node1, {1, 2}, {3, 4})
++ << createStatement(node1, {1, 2}, {3, 0, 4})
++ << false;
++ }
++
++ void tst_QShaderGraph::shouldTestStatementsEquality()
++ {
++ // GIVEN
++ QFETCH(QShaderGraph::Statement, left);
++ QFETCH(QShaderGraph::Statement, right);
++
++ // WHEN
++ const auto equal = (left == right);
++ const auto notEqual = (left != right);
++
++ // THEN
++ QFETCH(bool, expected);
++ QCOMPARE(equal, expected);
++ QCOMPARE(notEqual, !expected);
++ }
++
++ void tst_QShaderGraph::shouldFindIndexFromPortNameInStatements_data()
++ {
++ QTest::addColumn<QShaderGraph::Statement>("statement");
++ QTest::addColumn<QString>("portName");
++ QTest::addColumn<int>("expectedInputIndex");
++ QTest::addColumn<int>("expectedOutputIndex");
++
++ const auto inputNodeStatement = createStatement(createNode({
++ createPort(QShaderNodePort::Output, "input")
++ }));
++ const auto outputNodeStatement = createStatement(createNode({
++ createPort(QShaderNodePort::Input, "output")
++ }));
++ const auto functionNodeStatement = createStatement(createNode({
++ createPort(QShaderNodePort::Input, "input1"),
++ createPort(QShaderNodePort::Output, "output1"),
++ createPort(QShaderNodePort::Input, "input2"),
++ createPort(QShaderNodePort::Output, "output2"),
++ createPort(QShaderNodePort::Output, "output3"),
++ createPort(QShaderNodePort::Input, "input3")
++ }));
++
++ QTest::newRow("Invalid") << QShaderGraph::Statement() << "foo" << -1 << -1;
++ QTest::newRow("InputNodeWrongName") << inputNodeStatement << "foo" << -1 << -1;
++ QTest::newRow("InputNodeExistingName") << inputNodeStatement << "input" << -1 << 0;
++ QTest::newRow("OutputNodeWrongName") << outputNodeStatement << "foo" << -1 << -1;
++ QTest::newRow("OutputNodeExistingName") << outputNodeStatement << "output" << 0 << -1;
++ QTest::newRow("FunctionNodeWrongName") << functionNodeStatement << "foo" << -1 << -1;
++ QTest::newRow("FunctionNodeInput1") << functionNodeStatement << "input1" << 0 << -1;
++ QTest::newRow("FunctionNodeOutput1") << functionNodeStatement << "output1" << -1 << 0;
++ QTest::newRow("FunctionNodeInput2") << functionNodeStatement << "input2" << 1 << -1;
++ QTest::newRow("FunctionNodeOutput2") << functionNodeStatement << "output2" << -1 << 1;
++ QTest::newRow("FunctionNodeInput3") << functionNodeStatement << "input3" << 2 << -1;
++ QTest::newRow("FunctionNodeOutput3") << functionNodeStatement << "output3" << -1 << 2;
++ }
++
++ void tst_QShaderGraph::shouldFindIndexFromPortNameInStatements()
++ {
++ // GIVEN
++ QFETCH(QShaderGraph::Statement, statement);
++ QFETCH(QString, portName);
++ QFETCH(int, expectedInputIndex);
++ QFETCH(int, expectedOutputIndex);
++
++ // WHEN
++ const auto inputIndex = statement.portIndex(QShaderNodePort::Input, portName);
++ const auto outputIndex = statement.portIndex(QShaderNodePort::Output, portName);
++
++ // THEN
++ QCOMPARE(inputIndex, expectedInputIndex);
++ QCOMPARE(outputIndex, expectedOutputIndex);
++ }
++
++ void tst_QShaderGraph::shouldManageNodeList()
++ {
++ // GIVEN
++ const auto node1 = createNode({createPort(QShaderNodePort::Output, "node1")});
++ const auto node2 = createNode({createPort(QShaderNodePort::Output, "node2")});
++
++ auto graph = QShaderGraph();
++
++ // THEN (default state)
++ QVERIFY(graph.nodes().isEmpty());
++
++ // WHEN
++ graph.addNode(node1);
++
++ // THEN
++ QCOMPARE(graph.nodes().size(), 1);
++ QCOMPARE(graph.nodes().at(0).uuid(), node1.uuid());
++ QCOMPARE(graph.nodes().at(0).ports().at(0).name, node1.ports().at(0).name);
++
++ // WHEN
++ graph.addNode(node2);
++
++ // THEN
++ QCOMPARE(graph.nodes().size(), 2);
++ QCOMPARE(graph.nodes().at(0).uuid(), node1.uuid());
++ QCOMPARE(graph.nodes().at(0).ports().at(0).name, node1.ports().at(0).name);
++ QCOMPARE(graph.nodes().at(1).uuid(), node2.uuid());
++ QCOMPARE(graph.nodes().at(1).ports().at(0).name, node2.ports().at(0).name);
++
++
++ // WHEN
++ graph.removeNode(node2);
++
++ // THEN
++ QCOMPARE(graph.nodes().size(), 1);
++ QCOMPARE(graph.nodes().at(0).uuid(), node1.uuid());
++ QCOMPARE(graph.nodes().at(0).ports().at(0).name, node1.ports().at(0).name);
++
++ // WHEN
++ graph.addNode(node2);
++
++ // THEN
++ QCOMPARE(graph.nodes().size(), 2);
++ QCOMPARE(graph.nodes().at(0).uuid(), node1.uuid());
++ QCOMPARE(graph.nodes().at(0).ports().at(0).name, node1.ports().at(0).name);
++ QCOMPARE(graph.nodes().at(1).uuid(), node2.uuid());
++ QCOMPARE(graph.nodes().at(1).ports().at(0).name, node2.ports().at(0).name);
++
++ // WHEN
++ const auto node1bis = [node1] {
++ auto res = node1;
++ auto port = res.ports().at(0);
++ port.name = QStringLiteral("node1bis");
++ res.addPort(port);
++ return res;
++ }();
++ graph.addNode(node1bis);
++
++ // THEN
++ QCOMPARE(graph.nodes().size(), 2);
++ QCOMPARE(graph.nodes().at(0).uuid(), node2.uuid());
++ QCOMPARE(graph.nodes().at(0).ports().at(0).name, node2.ports().at(0).name);
++ QCOMPARE(graph.nodes().at(1).uuid(), node1bis.uuid());
++ QCOMPARE(graph.nodes().at(1).ports().at(0).name, node1bis.ports().at(0).name);
++ }
++
++ void tst_QShaderGraph::shouldManageEdgeList()
++ {
++ // GIVEN
++ const auto edge1 = createEdge(QUuid::createUuid(), "foo", QUuid::createUuid(), "bar");
++ const auto edge2 = createEdge(QUuid::createUuid(), "baz", QUuid::createUuid(), "boo");
++
++ auto graph = QShaderGraph();
++
++ // THEN (default state)
++ QVERIFY(graph.edges().isEmpty());
++
++ // WHEN
++ graph.addEdge(edge1);
++
++ // THEN
++ QCOMPARE(graph.edges().size(), 1);
++ QCOMPARE(graph.edges().at(0), edge1);
++
++ // WHEN
++ graph.addEdge(edge2);
++
++ // THEN
++ QCOMPARE(graph.edges().size(), 2);
++ QCOMPARE(graph.edges().at(0), edge1);
++ QCOMPARE(graph.edges().at(1), edge2);
++
++
++ // WHEN
++ graph.removeEdge(edge2);
++
++ // THEN
++ QCOMPARE(graph.edges().size(), 1);
++ QCOMPARE(graph.edges().at(0), edge1);
++
++ // WHEN
++ graph.addEdge(edge2);
++
++ // THEN
++ QCOMPARE(graph.edges().size(), 2);
++ QCOMPARE(graph.edges().at(0), edge1);
++ QCOMPARE(graph.edges().at(1), edge2);
++
++ // WHEN
++ graph.addEdge(edge1);
++
++ // THEN
++ QCOMPARE(graph.edges().size(), 2);
++ QCOMPARE(graph.edges().at(0), edge1);
++ QCOMPARE(graph.edges().at(1), edge2);
++ }
++
++ void tst_QShaderGraph::shouldSerializeGraphForCodeGeneration()
++ {
++ // GIVEN
++ const auto input1 = createNode({
++ createPort(QShaderNodePort::Output, "input1Value")
++ });
++ const auto input2 = createNode({
++ createPort(QShaderNodePort::Output, "input2Value")
++ });
++ const auto output1 = createNode({
++ createPort(QShaderNodePort::Input, "output1Value")
++ });
++ const auto output2 = createNode({
++ createPort(QShaderNodePort::Input, "output2Value")
++ });
++ const auto function1 = createNode({
++ createPort(QShaderNodePort::Input, "function1Input"),
++ createPort(QShaderNodePort::Output, "function1Output")
++ });
++ const auto function2 = createNode({
++ createPort(QShaderNodePort::Input, "function2Input1"),
++ createPort(QShaderNodePort::Input, "function2Input2"),
++ createPort(QShaderNodePort::Output, "function2Output")
++ });
++ const auto function3 = createNode({
++ createPort(QShaderNodePort::Input, "function3Input1"),
++ createPort(QShaderNodePort::Input, "function3Input2"),
++ createPort(QShaderNodePort::Output, "function3Output1"),
++ createPort(QShaderNodePort::Output, "function3Output2")
++ });
++
++ const auto graph = [=] {
++ auto res = QShaderGraph();
++ res.addNode(input1);
++ res.addNode(input2);
++ res.addNode(output1);
++ res.addNode(output2);
++ res.addNode(function1);
++ res.addNode(function2);
++ res.addNode(function3);
++ res.addEdge(createEdge(input1.uuid(), "input1Value", function1.uuid(), "function1Input"));
++ res.addEdge(createEdge(input1.uuid(), "input1Value", function2.uuid(), "function2Input1"));
++ res.addEdge(createEdge(input2.uuid(), "input2Value", function2.uuid(), "function2Input2"));
++ res.addEdge(createEdge(function1.uuid(), "function1Output", function3.uuid(), "function3Input1"));
++ res.addEdge(createEdge(function2.uuid(), "function2Output", function3.uuid(), "function3Input2"));
++ res.addEdge(createEdge(function3.uuid(), "function3Output1", output1.uuid(), "output1Value"));
++ res.addEdge(createEdge(function3.uuid(), "function3Output2", output2.uuid(), "output2Value"));
++ return res;
++ }();
++
++ // WHEN
++ const auto statements = graph.createStatements();
++
++ // THEN
++ const QList<QShaderGraph::Statement> expected
++ = { createStatement(input2, {}, {1}),
++ createStatement(input1, {}, {0}),
++ createStatement(function2, {0, 1}, {3}),
++ createStatement(function1, {0}, {2}),
++ createStatement(function3, {2, 3}, {4, 5}),
++ createStatement(output2, {5}, {}),
++ createStatement(output1, {4}, {}) };
++ dumpStatementsIfNeeded(statements, expected);
++ QCOMPARE(statements, expected);
++ }
++
++ void tst_QShaderGraph::shouldHandleUnboundPortsDuringGraphSerialization()
++ {
++ // GIVEN
++ const auto input = createNode({
++ createPort(QShaderNodePort::Output, "input")
++ });
++ const auto unboundInput = createNode({
++ createPort(QShaderNodePort::Output, "unbound")
++ });
++ const auto output = createNode({
++ createPort(QShaderNodePort::Input, "output")
++ });
++ const auto unboundOutput = createNode({
++ createPort(QShaderNodePort::Input, "unbound")
++ });
++ const auto function = createNode({
++ createPort(QShaderNodePort::Input, "functionInput1"),
++ createPort(QShaderNodePort::Input, "functionInput2"),
++ createPort(QShaderNodePort::Input, "functionInput3"),
++ createPort(QShaderNodePort::Output, "functionOutput1"),
++ createPort(QShaderNodePort::Output, "functionOutput2"),
++ createPort(QShaderNodePort::Output, "functionOutput3")
++ });
++
++ const auto graph = [=] {
++ auto res = QShaderGraph();
++ res.addNode(input);
++ res.addNode(unboundInput);
++ res.addNode(output);
++ res.addNode(unboundOutput);
++ res.addNode(function);
++ res.addEdge(createEdge(input.uuid(), "input", function.uuid(), "functionInput2"));
++ res.addEdge(createEdge(function.uuid(), "functionOutput2", output.uuid(), "output"));
++ return res;
++ }();
++
++ // WHEN
++ const auto statements = graph.createStatements();
++
++ // THEN
++ // Note that no statement has any unbound input
++ const QList<QShaderGraph::Statement> expected = { createStatement(input, {}, {0}) };
++ dumpStatementsIfNeeded(statements, expected);
++ QCOMPARE(statements, expected);
++ }
++
++ void tst_QShaderGraph::shouldSurviveCyclesDuringGraphSerialization()
++ {
++ // GIVEN
++ const auto input = createNode({
++ createPort(QShaderNodePort::Output, "input")
++ });
++ const auto output = createNode({
++ createPort(QShaderNodePort::Input, "output")
++ });
++ const auto function1 = createNode({
++ createPort(QShaderNodePort::Input, "function1Input1"),
++ createPort(QShaderNodePort::Input, "function1Input2"),
++ createPort(QShaderNodePort::Output, "function1Output")
++ });
++ const auto function2 = createNode({
++ createPort(QShaderNodePort::Input, "function2Input"),
++ createPort(QShaderNodePort::Output, "function2Output")
++ });
++ const auto function3 = createNode({
++ createPort(QShaderNodePort::Input, "function3Input"),
++ createPort(QShaderNodePort::Output, "function3Output")
++ });
++
++ const auto graph = [=] {
++ auto res = QShaderGraph();
++ res.addNode(input);
++ res.addNode(output);
++ res.addNode(function1);
++ res.addNode(function2);
++ res.addNode(function3);
++ res.addEdge(createEdge(input.uuid(), "input", function1.uuid(), "function1Input1"));
++ res.addEdge(createEdge(function1.uuid(), "function1Output", function2.uuid(), "function2Input"));
++ res.addEdge(createEdge(function2.uuid(), "function2Output", function3.uuid(), "function3Input"));
++ res.addEdge(createEdge(function3.uuid(), "function3Output", function1.uuid(), "function1Input2"));
++ res.addEdge(createEdge(function2.uuid(), "function2Output", output.uuid(), "output"));
++ return res;
++ }();
++
++ // WHEN
++ const auto statements = graph.createStatements();
++
++ // THEN
++ // The cycle is ignored
++ const QList<QShaderGraph::Statement> expected;
++ dumpStatementsIfNeeded(statements, expected);
++ QCOMPARE(statements, expected);
++ }
++
++ void tst_QShaderGraph::shouldDealWithEdgesJumpingOverLayers()
++ {
++ // GIVEN
++ const auto worldPosition = createNode({
++ createPort(QShaderNodePort::Output, "worldPosition")
++ });
++ const auto texture = createNode({
++ createPort(QShaderNodePort::Output, "texture")
++ });
++ const auto texCoord = createNode({
++ createPort(QShaderNodePort::Output, "texCoord")
++ });
++ const auto lightIntensity = createNode({
++ createPort(QShaderNodePort::Output, "lightIntensity")
++ });
++ const auto exposure = createNode({
++ createPort(QShaderNodePort::Output, "exposure")
++ });
++ const auto fragColor = createNode({
++ createPort(QShaderNodePort::Input, "fragColor")
++ });
++ const auto sampleTexture = createNode({
++ createPort(QShaderNodePort::Input, "sampler"),
++ createPort(QShaderNodePort::Input, "coord"),
++ createPort(QShaderNodePort::Output, "color")
++ });
++ const auto lightFunction = createNode({
++ createPort(QShaderNodePort::Input, "baseColor"),
++ createPort(QShaderNodePort::Input, "position"),
++ createPort(QShaderNodePort::Input, "lightIntensity"),
++ createPort(QShaderNodePort::Output, "outputColor")
++ });
++ const auto exposureFunction = createNode({
++ createPort(QShaderNodePort::Input, "inputColor"),
++ createPort(QShaderNodePort::Input, "exposure"),
++ createPort(QShaderNodePort::Output, "outputColor")
++ });
++
++ const auto graph = [=] {
++ auto res = QShaderGraph();
++
++ res.addNode(worldPosition);
++ res.addNode(texture);
++ res.addNode(texCoord);
++ res.addNode(lightIntensity);
++ res.addNode(exposure);
++ res.addNode(fragColor);
++ res.addNode(sampleTexture);
++ res.addNode(lightFunction);
++ res.addNode(exposureFunction);
++
++ res.addEdge(createEdge(texture.uuid(), "texture", sampleTexture.uuid(), "sampler"));
++ res.addEdge(createEdge(texCoord.uuid(), "texCoord", sampleTexture.uuid(), "coord"));
++
++ res.addEdge(createEdge(worldPosition.uuid(), "worldPosition", lightFunction.uuid(), "position"));
++ res.addEdge(createEdge(sampleTexture.uuid(), "color", lightFunction.uuid(), "baseColor"));
++ res.addEdge(createEdge(lightIntensity.uuid(), "lightIntensity", lightFunction.uuid(), "lightIntensity"));
++
++ res.addEdge(createEdge(lightFunction.uuid(), "outputColor", exposureFunction.uuid(), "inputColor"));
++ res.addEdge(createEdge(exposure.uuid(), "exposure", exposureFunction.uuid(), "exposure"));
++
++ res.addEdge(createEdge(exposureFunction.uuid(), "outputColor", fragColor.uuid(), "fragColor"));
++
++ return res;
++ }();
++
++ // WHEN
++ const auto statements = graph.createStatements();
++
++ // THEN
++ const QList<QShaderGraph::Statement> expected
++ = { createStatement(texCoord, {}, {2}),
++ createStatement(texture, {}, {1}),
++ createStatement(lightIntensity, {}, {3}),
++ createStatement(sampleTexture, {1, 2}, {5}),
++ createStatement(worldPosition, {}, {0}),
++ createStatement(exposure, {}, {4}),
++ createStatement(lightFunction, {5, 0, 3}, {6}),
++ createStatement(exposureFunction, {6, 4}, {7}),
++ createStatement(fragColor, {7}, {}) };
++ dumpStatementsIfNeeded(statements, expected);
++ QCOMPARE(statements, expected);
++ }
++
++ void tst_QShaderGraph::shouldGenerateDifferentStatementsDependingOnActiveLayers()
++ {
++ // GIVEN
++ const auto texCoord = createNode({
++ createPort(QShaderNodePort::Output, "texCoord")
++ }, {
++ "diffuseTexture",
++ "normalTexture"
++ });
++ const auto diffuseUniform = createNode({
++ createPort(QShaderNodePort::Output, "color")
++ }, {"diffuseUniform"});
++ const auto diffuseTexture = createNode({
++ createPort(QShaderNodePort::Input, "coord"),
++ createPort(QShaderNodePort::Output, "color")
++ }, {"diffuseTexture"});
++ const auto normalUniform = createNode({
++ createPort(QShaderNodePort::Output, "normal")
++ }, {"normalUniform"});
++ const auto normalTexture = createNode({
++ createPort(QShaderNodePort::Input, "coord"),
++ createPort(QShaderNodePort::Output, "normal")
++ }, {"normalTexture"});
++ const auto lightFunction = createNode({
++ createPort(QShaderNodePort::Input, "color"),
++ createPort(QShaderNodePort::Input, "normal"),
++ createPort(QShaderNodePort::Output, "output")
++ });
++ const auto fragColor = createNode({
++ createPort(QShaderNodePort::Input, "fragColor")
++ });
++
++ const auto graph = [=] {
++ auto res = QShaderGraph();
++
++ res.addNode(texCoord);
++ res.addNode(diffuseUniform);
++ res.addNode(diffuseTexture);
++ res.addNode(normalUniform);
++ res.addNode(normalTexture);
++ res.addNode(lightFunction);
++ res.addNode(fragColor);
++
++ res.addEdge(createEdge(diffuseUniform.uuid(), "color", lightFunction.uuid(), "color", {"diffuseUniform"}));
++ res.addEdge(createEdge(texCoord.uuid(), "texCoord", diffuseTexture.uuid(), "coord", {"diffuseTexture"}));
++ res.addEdge(createEdge(diffuseTexture.uuid(), "color", lightFunction.uuid(), "color", {"diffuseTexture"}));
++
++ res.addEdge(createEdge(normalUniform.uuid(), "normal", lightFunction.uuid(), "normal", {"normalUniform"}));
++ res.addEdge(createEdge(texCoord.uuid(), "texCoord", normalTexture.uuid(), "coord", {"normalTexture"}));
++ res.addEdge(createEdge(normalTexture.uuid(), "normal", lightFunction.uuid(), "normal", {"normalTexture"}));
++
++ res.addEdge(createEdge(lightFunction.uuid(), "output", fragColor.uuid(), "fragColor"));
++
++ return res;
++ }();
++
++ {
++ // WHEN
++ const auto statements = graph.createStatements({"diffuseUniform", "normalUniform"});
++
++ // THEN
++ const QList<QShaderGraph::Statement> expected
++ = { createStatement(normalUniform, {}, {1}),
++ createStatement(diffuseUniform, {}, {0}),
++ createStatement(lightFunction, {0, 1}, {2}),
++ createStatement(fragColor, {2}, {}) };
++ dumpStatementsIfNeeded(statements, expected);
++ QCOMPARE(statements, expected);
++ }
++
++ {
++ // WHEN
++ const auto statements = graph.createStatements({"diffuseUniform", "normalTexture"});
++
++ // THEN
++ const QList<QShaderGraph::Statement> expected
++ = { createStatement(texCoord, {}, {0}),
++ createStatement(normalTexture, {0}, {2}),
++ createStatement(diffuseUniform, {}, {1}),
++ createStatement(lightFunction, {1, 2}, {3}),
++ createStatement(fragColor, {3}, {}) };
++ dumpStatementsIfNeeded(statements, expected);
++ QCOMPARE(statements, expected);
++ }
++
++ {
++ // WHEN
++ const auto statements = graph.createStatements({"diffuseTexture", "normalUniform"});
++
++ // THEN
++ const QList<QShaderGraph::Statement> expected
++ = { createStatement(texCoord, {}, {0}),
++ createStatement(normalUniform, {}, {2}),
++ createStatement(diffuseTexture, {0}, {1}),
++ createStatement(lightFunction, {1, 2}, {3}),
++ createStatement(fragColor, {3}, {}) };
++ dumpStatementsIfNeeded(statements, expected);
++ QCOMPARE(statements, expected);
++ }
++
++ {
++ // WHEN
++ const auto statements = graph.createStatements({"diffuseTexture", "normalTexture"});
++
++ // THEN
++ const QList<QShaderGraph::Statement> expected
++ = { createStatement(texCoord, {}, {0}),
++ createStatement(normalTexture, {0}, {2}),
++ createStatement(diffuseTexture, {0}, {1}),
++ createStatement(lightFunction, {1, 2}, {3}),
++ createStatement(fragColor, {3}, {}) };
++ dumpStatementsIfNeeded(statements, expected);
++ QCOMPARE(statements, expected);
++ }
++ }
++
++ void tst_QShaderGraph::shouldDealWithBranchesWithoutOutput()
++ {
++ // GIVEN
++ const auto input = createNode({
++ createPort(QShaderNodePort::Output, "input")
++ });
++ const auto output = createNode({
++ createPort(QShaderNodePort::Input, "output")
++ });
++ const auto danglingFunction = createNode({
++ createPort(QShaderNodePort::Input, "functionInput"),
++ createPort(QShaderNodePort::Output, "unbound")
++ });
++ const auto function = createNode({
++ createPort(QShaderNodePort::Input, "functionInput"),
++ createPort(QShaderNodePort::Output, "functionOutput")
++ });
++
++ const auto graph = [=] {
++ auto res = QShaderGraph();
++ res.addNode(input);
++ res.addNode(function);
++ res.addNode(danglingFunction);
++ res.addNode(output);
++ res.addEdge(createEdge(input.uuid(), "input", function.uuid(), "functionInput"));
++ res.addEdge(createEdge(input.uuid(), "input", danglingFunction.uuid(), "functionInput"));
++ res.addEdge(createEdge(function.uuid(), "functionOutput", output.uuid(), "output"));
++ return res;
++ }();
++
++ // WHEN
++ const auto statements = graph.createStatements();
++
++ // THEN
++ // Note that no edge leads to the unbound input
++ const QList<QShaderGraph::Statement> expected
++ = { createStatement(input, {}, {0}),
++ createStatement(function, {0}, {1}),
++ createStatement(output, {1}, {}),
++ createStatement(danglingFunction, {0}, {2}) };
++ dumpStatementsIfNeeded(statements, expected);
++ QCOMPARE(statements, expected);
++ }
++
++ void tst_QShaderGraph::shouldDiscardEdgesConnectedToDiscardedNodes()
++ {
++ // GIVEN
++ const auto input = createNode({
++ createPort(QShaderNodePort::Output, "input")
++ });
++ const auto output = createNode({
++ createPort(QShaderNodePort::Input, "output")
++ });
++ const auto function0 = createNode({
++ createPort(QShaderNodePort::Input, "function0Input"),
++ createPort(QShaderNodePort::Output, "function0Output")
++ }, {"0"});
++ const auto function1 = createNode({
++ createPort(QShaderNodePort::Input, "function1Input"),
++ createPort(QShaderNodePort::Output, "function1Output")
++ }, {"1"});
++
++ const auto graph = [=] {
++ auto res = QShaderGraph();
++ res.addNode(input);
++ res.addNode(function0);
++ res.addNode(function1);
++ res.addNode(output);
++ res.addEdge(createEdge(input.uuid(), "input", function0.uuid(), "function0Input", {"0"}));
++ res.addEdge(createEdge(input.uuid(), "input", function1.uuid(), "function1Input"));
++ res.addEdge(createEdge(function0.uuid(), "function0Output", output.uuid(), "output"));
++ res.addEdge(createEdge(function1.uuid(), "function1Output", output.uuid(), "output"));
++ return res;
++ }();
++
++ // WHEN
++ const auto statements = graph.createStatements({"0"});
++
++ // THEN
++ const QList<QShaderGraph::Statement> expected
++ = { createStatement(input, {}, {0}),
++ createStatement(function0, {0}, {1}),
++ createStatement(output, {1}, {}) };
++ dumpStatementsIfNeeded(statements, expected);
++ QCOMPARE(statements, expected);
++ }
++
++ QTEST_MAIN(tst_QShaderGraph)
++
++ #include "tst_qshadergraph.moc"