1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB). |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the test suite of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
21 | ** included in the packaging of this file. Please review the following |
22 | ** information to ensure the GNU General Public License requirements will |
23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
24 | ** |
25 | ** $QT_END_LICENSE$ |
26 | ** |
27 | ****************************************************************************/ |
28 | |
29 | |
30 | #include <QtTest/QtTest> |
31 | |
32 | #include <Qt3DRender/private/qshadergraph_p.h> |
33 | |
34 | using namespace Qt3DRender; |
35 | namespace |
36 | { |
37 | QShaderNodePort createPort(QShaderNodePort::Direction portDirection, const QString &portName) |
38 | { |
39 | auto port = QShaderNodePort(); |
40 | port.direction = portDirection; |
41 | port.name = portName; |
42 | return port; |
43 | } |
44 | |
45 | QShaderNode createNode(const QVector<QShaderNodePort> &ports, const QStringList &layers = QStringList()) |
46 | { |
47 | auto node = QShaderNode(); |
48 | node.setUuid(QUuid::createUuid()); |
49 | node.setLayers(layers); |
50 | for (const auto &port : ports) |
51 | node.addPort(port); |
52 | return node; |
53 | } |
54 | |
55 | QShaderGraph::Edge createEdge(const QUuid &sourceUuid, const QString &sourceName, |
56 | const QUuid &targetUuid, const QString &targetName, |
57 | const QStringList &layers = QStringList()) |
58 | { |
59 | auto edge = QShaderGraph::Edge(); |
60 | edge.sourceNodeUuid = sourceUuid; |
61 | edge.sourcePortName = sourceName; |
62 | edge.targetNodeUuid = targetUuid; |
63 | edge.targetPortName = targetName; |
64 | edge.layers = layers; |
65 | return edge; |
66 | } |
67 | |
68 | QShaderGraph::Statement createStatement(const QShaderNode &node, |
69 | const QVector<int> &inputs = QVector<int>(), |
70 | const QVector<int> &outputs = QVector<int>()) |
71 | { |
72 | auto statement = QShaderGraph::Statement(); |
73 | statement.node = node; |
74 | statement.inputs = inputs; |
75 | statement.outputs = outputs; |
76 | return statement; |
77 | } |
78 | |
79 | void debugStatement(const QString &prefix, const QShaderGraph::Statement &statement) |
80 | { |
81 | qDebug() << prefix << statement.inputs << statement.uuid().toString() << statement.outputs; |
82 | } |
83 | |
84 | void dumpStatementsIfNeeded(const QVector<QShaderGraph::Statement> &statements, const QVector<QShaderGraph::Statement> &expected) |
85 | { |
86 | if (statements != expected) { |
87 | for (int i = 0; i < qMax(a: statements.size(), b: expected.size()); i++) { |
88 | qDebug() << "----" << i << "----" ; |
89 | if (i < statements.size()) |
90 | debugStatement(prefix: "A:" , statement: statements.at(i)); |
91 | if (i < expected.size()) |
92 | debugStatement(prefix: "E:" , statement: expected.at(i)); |
93 | qDebug() << "-----------" ; |
94 | } |
95 | } |
96 | } |
97 | } |
98 | |
99 | class tst_QShaderGraph : public QObject |
100 | { |
101 | Q_OBJECT |
102 | private slots: |
103 | void shouldHaveEdgeDefaultState(); |
104 | void shouldTestEdgesEquality_data(); |
105 | void shouldTestEdgesEquality(); |
106 | void shouldManipulateStatementMembers(); |
107 | void shouldTestStatementsEquality_data(); |
108 | void shouldTestStatementsEquality(); |
109 | void shouldFindIndexFromPortNameInStatements_data(); |
110 | void shouldFindIndexFromPortNameInStatements(); |
111 | void shouldManageNodeList(); |
112 | void shouldManageEdgeList(); |
113 | void shouldSerializeGraphForCodeGeneration(); |
114 | void shouldHandleUnboundPortsDuringGraphSerialization(); |
115 | void shouldSurviveCyclesDuringGraphSerialization(); |
116 | void shouldDealWithEdgesJumpingOverLayers(); |
117 | void shouldGenerateDifferentStatementsDependingOnActiveLayers(); |
118 | void shouldDealWithBranchesWithoutOutput(); |
119 | void shouldDiscardEdgesConnectedToDiscardedNodes(); |
120 | }; |
121 | |
122 | void tst_QShaderGraph::shouldHaveEdgeDefaultState() |
123 | { |
124 | // GIVEN |
125 | auto edge = QShaderGraph::Edge(); |
126 | |
127 | // THEN |
128 | QVERIFY(edge.sourceNodeUuid.isNull()); |
129 | QVERIFY(edge.sourcePortName.isEmpty()); |
130 | QVERIFY(edge.targetNodeUuid.isNull()); |
131 | QVERIFY(edge.targetPortName.isEmpty()); |
132 | } |
133 | |
134 | void tst_QShaderGraph::shouldTestEdgesEquality_data() |
135 | { |
136 | QTest::addColumn<QShaderGraph::Edge>(name: "left" ); |
137 | QTest::addColumn<QShaderGraph::Edge>(name: "right" ); |
138 | QTest::addColumn<bool>(name: "expected" ); |
139 | |
140 | const auto sourceUuid1 = QUuid::createUuid(); |
141 | const auto sourceUuid2 = QUuid::createUuid(); |
142 | const auto targetUuid1 = QUuid::createUuid(); |
143 | const auto targetUuid2 = QUuid::createUuid(); |
144 | |
145 | QTest::newRow(dataTag: "Equals" ) << createEdge(sourceUuid: sourceUuid1, sourceName: "foo" , targetUuid: targetUuid1, targetName: "bar" ) |
146 | << createEdge(sourceUuid: sourceUuid1, sourceName: "foo" , targetUuid: targetUuid1, targetName: "bar" ) |
147 | << true; |
148 | QTest::newRow(dataTag: "SourceUuid" ) << createEdge(sourceUuid: sourceUuid1, sourceName: "foo" , targetUuid: targetUuid1, targetName: "bar" ) |
149 | << createEdge(sourceUuid: sourceUuid2, sourceName: "foo" , targetUuid: targetUuid1, targetName: "bar" ) |
150 | << false; |
151 | QTest::newRow(dataTag: "SourceName" ) << createEdge(sourceUuid: sourceUuid1, sourceName: "foo" , targetUuid: targetUuid1, targetName: "bar" ) |
152 | << createEdge(sourceUuid: sourceUuid1, sourceName: "bleh" , targetUuid: targetUuid1, targetName: "bar" ) |
153 | << false; |
154 | QTest::newRow(dataTag: "TargetUuid" ) << createEdge(sourceUuid: sourceUuid1, sourceName: "foo" , targetUuid: targetUuid1, targetName: "bar" ) |
155 | << createEdge(sourceUuid: sourceUuid1, sourceName: "foo" , targetUuid: targetUuid2, targetName: "bar" ) |
156 | << false; |
157 | QTest::newRow(dataTag: "TargetName" ) << createEdge(sourceUuid: sourceUuid1, sourceName: "foo" , targetUuid: targetUuid1, targetName: "bar" ) |
158 | << createEdge(sourceUuid: sourceUuid1, sourceName: "foo" , targetUuid: targetUuid1, targetName: "bleh" ) |
159 | << false; |
160 | } |
161 | |
162 | void tst_QShaderGraph::shouldTestEdgesEquality() |
163 | { |
164 | // GIVEN |
165 | QFETCH(QShaderGraph::Edge, left); |
166 | QFETCH(QShaderGraph::Edge, right); |
167 | |
168 | // WHEN |
169 | const auto equal = (left == right); |
170 | const auto notEqual = (left != right); |
171 | |
172 | // THEN |
173 | QFETCH(bool, expected); |
174 | QCOMPARE(equal, expected); |
175 | QCOMPARE(notEqual, !expected); |
176 | } |
177 | |
178 | void tst_QShaderGraph::shouldManipulateStatementMembers() |
179 | { |
180 | // GIVEN |
181 | auto statement = QShaderGraph::Statement(); |
182 | |
183 | // THEN (default state) |
184 | QVERIFY(statement.inputs.isEmpty()); |
185 | QVERIFY(statement.outputs.isEmpty()); |
186 | QVERIFY(statement.node.uuid().isNull()); |
187 | QVERIFY(statement.uuid().isNull()); |
188 | |
189 | // WHEN |
190 | const auto node = createNode(ports: {}); |
191 | statement.node = node; |
192 | |
193 | // THEN |
194 | QCOMPARE(statement.uuid(), node.uuid()); |
195 | |
196 | // WHEN |
197 | statement.node = QShaderNode(); |
198 | |
199 | // THEN |
200 | QVERIFY(statement.uuid().isNull()); |
201 | } |
202 | |
203 | void tst_QShaderGraph::shouldTestStatementsEquality_data() |
204 | { |
205 | QTest::addColumn<QShaderGraph::Statement>(name: "left" ); |
206 | QTest::addColumn<QShaderGraph::Statement>(name: "right" ); |
207 | QTest::addColumn<bool>(name: "expected" ); |
208 | |
209 | const auto node1 = createNode(ports: {}); |
210 | const auto node2 = createNode(ports: {}); |
211 | |
212 | QTest::newRow(dataTag: "EqualNodes" ) << createStatement(node: node1, inputs: {1, 2}, outputs: {3, 4}) |
213 | << createStatement(node: node1, inputs: {1, 2}, outputs: {3, 4}) |
214 | << true; |
215 | QTest::newRow(dataTag: "EqualInvalids" ) << createStatement(node: QShaderNode(), inputs: {1, 2}, outputs: {3, 4}) |
216 | << createStatement(node: QShaderNode(), inputs: {1, 2}, outputs: {3, 4}) |
217 | << true; |
218 | QTest::newRow(dataTag: "Nodes" ) << createStatement(node: node1, inputs: {1, 2}, outputs: {3, 4}) |
219 | << createStatement(node: node2, inputs: {1, 2}, outputs: {3, 4}) |
220 | << false; |
221 | QTest::newRow(dataTag: "Inputs" ) << createStatement(node: node1, inputs: {1, 2}, outputs: {3, 4}) |
222 | << createStatement(node: node1, inputs: {1, 2, 0}, outputs: {3, 4}) |
223 | << false; |
224 | QTest::newRow(dataTag: "Outputs" ) << createStatement(node: node1, inputs: {1, 2}, outputs: {3, 4}) |
225 | << createStatement(node: node1, inputs: {1, 2}, outputs: {3, 0, 4}) |
226 | << false; |
227 | } |
228 | |
229 | void tst_QShaderGraph::shouldTestStatementsEquality() |
230 | { |
231 | // GIVEN |
232 | QFETCH(QShaderGraph::Statement, left); |
233 | QFETCH(QShaderGraph::Statement, right); |
234 | |
235 | // WHEN |
236 | const auto equal = (left == right); |
237 | const auto notEqual = (left != right); |
238 | |
239 | // THEN |
240 | QFETCH(bool, expected); |
241 | QCOMPARE(equal, expected); |
242 | QCOMPARE(notEqual, !expected); |
243 | } |
244 | |
245 | void tst_QShaderGraph::shouldFindIndexFromPortNameInStatements_data() |
246 | { |
247 | QTest::addColumn<QShaderGraph::Statement>(name: "statement" ); |
248 | QTest::addColumn<QString>(name: "portName" ); |
249 | QTest::addColumn<int>(name: "expectedInputIndex" ); |
250 | QTest::addColumn<int>(name: "expectedOutputIndex" ); |
251 | |
252 | const auto inputNodeStatement = createStatement(node: createNode(ports: { |
253 | createPort(portDirection: QShaderNodePort::Output, portName: "input" ) |
254 | })); |
255 | const auto outputNodeStatement = createStatement(node: createNode(ports: { |
256 | createPort(portDirection: QShaderNodePort::Input, portName: "output" ) |
257 | })); |
258 | const auto functionNodeStatement = createStatement(node: createNode(ports: { |
259 | createPort(portDirection: QShaderNodePort::Input, portName: "input1" ), |
260 | createPort(portDirection: QShaderNodePort::Output, portName: "output1" ), |
261 | createPort(portDirection: QShaderNodePort::Input, portName: "input2" ), |
262 | createPort(portDirection: QShaderNodePort::Output, portName: "output2" ), |
263 | createPort(portDirection: QShaderNodePort::Output, portName: "output3" ), |
264 | createPort(portDirection: QShaderNodePort::Input, portName: "input3" ) |
265 | })); |
266 | |
267 | QTest::newRow(dataTag: "Invalid" ) << QShaderGraph::Statement() << "foo" << -1 << -1; |
268 | QTest::newRow(dataTag: "InputNodeWrongName" ) << inputNodeStatement << "foo" << -1 << -1; |
269 | QTest::newRow(dataTag: "InputNodeExistingName" ) << inputNodeStatement << "input" << -1 << 0; |
270 | QTest::newRow(dataTag: "OutputNodeWrongName" ) << outputNodeStatement << "foo" << -1 << -1; |
271 | QTest::newRow(dataTag: "OutputNodeExistingName" ) << outputNodeStatement << "output" << 0 << -1; |
272 | QTest::newRow(dataTag: "FunctionNodeWrongName" ) << functionNodeStatement << "foo" << -1 << -1; |
273 | QTest::newRow(dataTag: "FunctionNodeInput1" ) << functionNodeStatement << "input1" << 0 << -1; |
274 | QTest::newRow(dataTag: "FunctionNodeOutput1" ) << functionNodeStatement << "output1" << -1 << 0; |
275 | QTest::newRow(dataTag: "FunctionNodeInput2" ) << functionNodeStatement << "input2" << 1 << -1; |
276 | QTest::newRow(dataTag: "FunctionNodeOutput2" ) << functionNodeStatement << "output2" << -1 << 1; |
277 | QTest::newRow(dataTag: "FunctionNodeInput3" ) << functionNodeStatement << "input3" << 2 << -1; |
278 | QTest::newRow(dataTag: "FunctionNodeOutput3" ) << functionNodeStatement << "output3" << -1 << 2; |
279 | } |
280 | |
281 | void tst_QShaderGraph::shouldFindIndexFromPortNameInStatements() |
282 | { |
283 | // GIVEN |
284 | QFETCH(QShaderGraph::Statement, statement); |
285 | QFETCH(QString, portName); |
286 | QFETCH(int, expectedInputIndex); |
287 | QFETCH(int, expectedOutputIndex); |
288 | |
289 | // WHEN |
290 | const auto inputIndex = statement.portIndex(direction: QShaderNodePort::Input, portName); |
291 | const auto outputIndex = statement.portIndex(direction: QShaderNodePort::Output, portName); |
292 | |
293 | // THEN |
294 | QCOMPARE(inputIndex, expectedInputIndex); |
295 | QCOMPARE(outputIndex, expectedOutputIndex); |
296 | } |
297 | |
298 | void tst_QShaderGraph::shouldManageNodeList() |
299 | { |
300 | // GIVEN |
301 | const auto node1 = createNode(ports: {createPort(portDirection: QShaderNodePort::Output, portName: "node1" )}); |
302 | const auto node2 = createNode(ports: {createPort(portDirection: QShaderNodePort::Output, portName: "node2" )}); |
303 | |
304 | auto graph = QShaderGraph(); |
305 | |
306 | // THEN (default state) |
307 | QVERIFY(graph.nodes().isEmpty()); |
308 | |
309 | // WHEN |
310 | graph.addNode(node: node1); |
311 | |
312 | // THEN |
313 | QCOMPARE(graph.nodes().size(), 1); |
314 | QCOMPARE(graph.nodes().at(0).uuid(), node1.uuid()); |
315 | QCOMPARE(graph.nodes().at(0).ports().at(0).name, node1.ports().at(0).name); |
316 | |
317 | // WHEN |
318 | graph.addNode(node: node2); |
319 | |
320 | // THEN |
321 | QCOMPARE(graph.nodes().size(), 2); |
322 | QCOMPARE(graph.nodes().at(0).uuid(), node1.uuid()); |
323 | QCOMPARE(graph.nodes().at(0).ports().at(0).name, node1.ports().at(0).name); |
324 | QCOMPARE(graph.nodes().at(1).uuid(), node2.uuid()); |
325 | QCOMPARE(graph.nodes().at(1).ports().at(0).name, node2.ports().at(0).name); |
326 | |
327 | |
328 | // WHEN |
329 | graph.removeNode(node: node2); |
330 | |
331 | // THEN |
332 | QCOMPARE(graph.nodes().size(), 1); |
333 | QCOMPARE(graph.nodes().at(0).uuid(), node1.uuid()); |
334 | QCOMPARE(graph.nodes().at(0).ports().at(0).name, node1.ports().at(0).name); |
335 | |
336 | // WHEN |
337 | graph.addNode(node: node2); |
338 | |
339 | // THEN |
340 | QCOMPARE(graph.nodes().size(), 2); |
341 | QCOMPARE(graph.nodes().at(0).uuid(), node1.uuid()); |
342 | QCOMPARE(graph.nodes().at(0).ports().at(0).name, node1.ports().at(0).name); |
343 | QCOMPARE(graph.nodes().at(1).uuid(), node2.uuid()); |
344 | QCOMPARE(graph.nodes().at(1).ports().at(0).name, node2.ports().at(0).name); |
345 | |
346 | // WHEN |
347 | const auto node1bis = [node1] { |
348 | auto res = node1; |
349 | auto port = res.ports().at(i: 0); |
350 | port.name = QStringLiteral("node1bis" ); |
351 | res.addPort(port); |
352 | return res; |
353 | }(); |
354 | graph.addNode(node: node1bis); |
355 | |
356 | // THEN |
357 | QCOMPARE(graph.nodes().size(), 2); |
358 | QCOMPARE(graph.nodes().at(0).uuid(), node2.uuid()); |
359 | QCOMPARE(graph.nodes().at(0).ports().at(0).name, node2.ports().at(0).name); |
360 | QCOMPARE(graph.nodes().at(1).uuid(), node1bis.uuid()); |
361 | QCOMPARE(graph.nodes().at(1).ports().at(0).name, node1bis.ports().at(0).name); |
362 | } |
363 | |
364 | void tst_QShaderGraph::shouldManageEdgeList() |
365 | { |
366 | // GIVEN |
367 | const auto edge1 = createEdge(sourceUuid: QUuid::createUuid(), sourceName: "foo" , targetUuid: QUuid::createUuid(), targetName: "bar" ); |
368 | const auto edge2 = createEdge(sourceUuid: QUuid::createUuid(), sourceName: "baz" , targetUuid: QUuid::createUuid(), targetName: "boo" ); |
369 | |
370 | auto graph = QShaderGraph(); |
371 | |
372 | // THEN (default state) |
373 | QVERIFY(graph.edges().isEmpty()); |
374 | |
375 | // WHEN |
376 | graph.addEdge(edge: edge1); |
377 | |
378 | // THEN |
379 | QCOMPARE(graph.edges().size(), 1); |
380 | QCOMPARE(graph.edges().at(0), edge1); |
381 | |
382 | // WHEN |
383 | graph.addEdge(edge: edge2); |
384 | |
385 | // THEN |
386 | QCOMPARE(graph.edges().size(), 2); |
387 | QCOMPARE(graph.edges().at(0), edge1); |
388 | QCOMPARE(graph.edges().at(1), edge2); |
389 | |
390 | |
391 | // WHEN |
392 | graph.removeEdge(edge: edge2); |
393 | |
394 | // THEN |
395 | QCOMPARE(graph.edges().size(), 1); |
396 | QCOMPARE(graph.edges().at(0), edge1); |
397 | |
398 | // WHEN |
399 | graph.addEdge(edge: edge2); |
400 | |
401 | // THEN |
402 | QCOMPARE(graph.edges().size(), 2); |
403 | QCOMPARE(graph.edges().at(0), edge1); |
404 | QCOMPARE(graph.edges().at(1), edge2); |
405 | |
406 | // WHEN |
407 | graph.addEdge(edge: edge1); |
408 | |
409 | // THEN |
410 | QCOMPARE(graph.edges().size(), 2); |
411 | QCOMPARE(graph.edges().at(0), edge1); |
412 | QCOMPARE(graph.edges().at(1), edge2); |
413 | } |
414 | |
415 | void tst_QShaderGraph::shouldSerializeGraphForCodeGeneration() |
416 | { |
417 | // GIVEN |
418 | const auto input1 = createNode(ports: { |
419 | createPort(portDirection: QShaderNodePort::Output, portName: "input1Value" ) |
420 | }); |
421 | const auto input2 = createNode(ports: { |
422 | createPort(portDirection: QShaderNodePort::Output, portName: "input2Value" ) |
423 | }); |
424 | const auto output1 = createNode(ports: { |
425 | createPort(portDirection: QShaderNodePort::Input, portName: "output1Value" ) |
426 | }); |
427 | const auto output2 = createNode(ports: { |
428 | createPort(portDirection: QShaderNodePort::Input, portName: "output2Value" ) |
429 | }); |
430 | const auto function1 = createNode(ports: { |
431 | createPort(portDirection: QShaderNodePort::Input, portName: "function1Input" ), |
432 | createPort(portDirection: QShaderNodePort::Output, portName: "function1Output" ) |
433 | }); |
434 | const auto function2 = createNode(ports: { |
435 | createPort(portDirection: QShaderNodePort::Input, portName: "function2Input1" ), |
436 | createPort(portDirection: QShaderNodePort::Input, portName: "function2Input2" ), |
437 | createPort(portDirection: QShaderNodePort::Output, portName: "function2Output" ) |
438 | }); |
439 | const auto function3 = createNode(ports: { |
440 | createPort(portDirection: QShaderNodePort::Input, portName: "function3Input1" ), |
441 | createPort(portDirection: QShaderNodePort::Input, portName: "function3Input2" ), |
442 | createPort(portDirection: QShaderNodePort::Output, portName: "function3Output1" ), |
443 | createPort(portDirection: QShaderNodePort::Output, portName: "function3Output2" ) |
444 | }); |
445 | |
446 | const auto graph = [=] { |
447 | auto res = QShaderGraph(); |
448 | res.addNode(node: input1); |
449 | res.addNode(node: input2); |
450 | res.addNode(node: output1); |
451 | res.addNode(node: output2); |
452 | res.addNode(node: function1); |
453 | res.addNode(node: function2); |
454 | res.addNode(node: function3); |
455 | res.addEdge(edge: createEdge(sourceUuid: input1.uuid(), sourceName: "input1Value" , targetUuid: function1.uuid(), targetName: "function1Input" )); |
456 | res.addEdge(edge: createEdge(sourceUuid: input1.uuid(), sourceName: "input1Value" , targetUuid: function2.uuid(), targetName: "function2Input1" )); |
457 | res.addEdge(edge: createEdge(sourceUuid: input2.uuid(), sourceName: "input2Value" , targetUuid: function2.uuid(), targetName: "function2Input2" )); |
458 | res.addEdge(edge: createEdge(sourceUuid: function1.uuid(), sourceName: "function1Output" , targetUuid: function3.uuid(), targetName: "function3Input1" )); |
459 | res.addEdge(edge: createEdge(sourceUuid: function2.uuid(), sourceName: "function2Output" , targetUuid: function3.uuid(), targetName: "function3Input2" )); |
460 | res.addEdge(edge: createEdge(sourceUuid: function3.uuid(), sourceName: "function3Output1" , targetUuid: output1.uuid(), targetName: "output1Value" )); |
461 | res.addEdge(edge: createEdge(sourceUuid: function3.uuid(), sourceName: "function3Output2" , targetUuid: output2.uuid(), targetName: "output2Value" )); |
462 | return res; |
463 | }(); |
464 | |
465 | // WHEN |
466 | const auto statements = graph.createStatements(); |
467 | |
468 | // THEN |
469 | const auto expected = QVector<QShaderGraph::Statement>() |
470 | << createStatement(node: input2, inputs: {}, outputs: {1}) |
471 | << createStatement(node: input1, inputs: {}, outputs: {0}) |
472 | << createStatement(node: function2, inputs: {0, 1}, outputs: {3}) |
473 | << createStatement(node: function1, inputs: {0}, outputs: {2}) |
474 | << createStatement(node: function3, inputs: {2, 3}, outputs: {4, 5}) |
475 | << createStatement(node: output2, inputs: {5}, outputs: {}) |
476 | << createStatement(node: output1, inputs: {4}, outputs: {}); |
477 | dumpStatementsIfNeeded(statements, expected); |
478 | QCOMPARE(statements, expected); |
479 | } |
480 | |
481 | void tst_QShaderGraph::shouldHandleUnboundPortsDuringGraphSerialization() |
482 | { |
483 | // GIVEN |
484 | const auto input = createNode(ports: { |
485 | createPort(portDirection: QShaderNodePort::Output, portName: "input" ) |
486 | }); |
487 | const auto unboundInput = createNode(ports: { |
488 | createPort(portDirection: QShaderNodePort::Output, portName: "unbound" ) |
489 | }); |
490 | const auto output = createNode(ports: { |
491 | createPort(portDirection: QShaderNodePort::Input, portName: "output" ) |
492 | }); |
493 | const auto unboundOutput = createNode(ports: { |
494 | createPort(portDirection: QShaderNodePort::Input, portName: "unbound" ) |
495 | }); |
496 | const auto function = createNode(ports: { |
497 | createPort(portDirection: QShaderNodePort::Input, portName: "functionInput1" ), |
498 | createPort(portDirection: QShaderNodePort::Input, portName: "functionInput2" ), |
499 | createPort(portDirection: QShaderNodePort::Input, portName: "functionInput3" ), |
500 | createPort(portDirection: QShaderNodePort::Output, portName: "functionOutput1" ), |
501 | createPort(portDirection: QShaderNodePort::Output, portName: "functionOutput2" ), |
502 | createPort(portDirection: QShaderNodePort::Output, portName: "functionOutput3" ) |
503 | }); |
504 | |
505 | const auto graph = [=] { |
506 | auto res = QShaderGraph(); |
507 | res.addNode(node: input); |
508 | res.addNode(node: unboundInput); |
509 | res.addNode(node: output); |
510 | res.addNode(node: unboundOutput); |
511 | res.addNode(node: function); |
512 | res.addEdge(edge: createEdge(sourceUuid: input.uuid(), sourceName: "input" , targetUuid: function.uuid(), targetName: "functionInput2" )); |
513 | res.addEdge(edge: createEdge(sourceUuid: function.uuid(), sourceName: "functionOutput2" , targetUuid: output.uuid(), targetName: "output" )); |
514 | return res; |
515 | }(); |
516 | |
517 | // WHEN |
518 | const auto statements = graph.createStatements(); |
519 | |
520 | // THEN |
521 | // Note that no statement has any unbound input |
522 | const auto expected = QVector<QShaderGraph::Statement>() |
523 | << createStatement(node: input, inputs: {}, outputs: {0}); |
524 | dumpStatementsIfNeeded(statements, expected); |
525 | QCOMPARE(statements, expected); |
526 | } |
527 | |
528 | void tst_QShaderGraph::shouldSurviveCyclesDuringGraphSerialization() |
529 | { |
530 | // GIVEN |
531 | const auto input = createNode(ports: { |
532 | createPort(portDirection: QShaderNodePort::Output, portName: "input" ) |
533 | }); |
534 | const auto output = createNode(ports: { |
535 | createPort(portDirection: QShaderNodePort::Input, portName: "output" ) |
536 | }); |
537 | const auto function1 = createNode(ports: { |
538 | createPort(portDirection: QShaderNodePort::Input, portName: "function1Input1" ), |
539 | createPort(portDirection: QShaderNodePort::Input, portName: "function1Input2" ), |
540 | createPort(portDirection: QShaderNodePort::Output, portName: "function1Output" ) |
541 | }); |
542 | const auto function2 = createNode(ports: { |
543 | createPort(portDirection: QShaderNodePort::Input, portName: "function2Input" ), |
544 | createPort(portDirection: QShaderNodePort::Output, portName: "function2Output" ) |
545 | }); |
546 | const auto function3 = createNode(ports: { |
547 | createPort(portDirection: QShaderNodePort::Input, portName: "function3Input" ), |
548 | createPort(portDirection: QShaderNodePort::Output, portName: "function3Output" ) |
549 | }); |
550 | |
551 | const auto graph = [=] { |
552 | auto res = QShaderGraph(); |
553 | res.addNode(node: input); |
554 | res.addNode(node: output); |
555 | res.addNode(node: function1); |
556 | res.addNode(node: function2); |
557 | res.addNode(node: function3); |
558 | res.addEdge(edge: createEdge(sourceUuid: input.uuid(), sourceName: "input" , targetUuid: function1.uuid(), targetName: "function1Input1" )); |
559 | res.addEdge(edge: createEdge(sourceUuid: function1.uuid(), sourceName: "function1Output" , targetUuid: function2.uuid(), targetName: "function2Input" )); |
560 | res.addEdge(edge: createEdge(sourceUuid: function2.uuid(), sourceName: "function2Output" , targetUuid: function3.uuid(), targetName: "function3Input" )); |
561 | res.addEdge(edge: createEdge(sourceUuid: function3.uuid(), sourceName: "function3Output" , targetUuid: function1.uuid(), targetName: "function1Input2" )); |
562 | res.addEdge(edge: createEdge(sourceUuid: function2.uuid(), sourceName: "function2Output" , targetUuid: output.uuid(), targetName: "output" )); |
563 | return res; |
564 | }(); |
565 | |
566 | // WHEN |
567 | const auto statements = graph.createStatements(); |
568 | |
569 | // THEN |
570 | // The cycle is ignored |
571 | const auto expected = QVector<QShaderGraph::Statement>(); |
572 | dumpStatementsIfNeeded(statements, expected); |
573 | QCOMPARE(statements, expected); |
574 | } |
575 | |
576 | void tst_QShaderGraph::shouldDealWithEdgesJumpingOverLayers() |
577 | { |
578 | // GIVEN |
579 | const auto worldPosition = createNode(ports: { |
580 | createPort(portDirection: QShaderNodePort::Output, portName: "worldPosition" ) |
581 | }); |
582 | const auto texture = createNode(ports: { |
583 | createPort(portDirection: QShaderNodePort::Output, portName: "texture" ) |
584 | }); |
585 | const auto texCoord = createNode(ports: { |
586 | createPort(portDirection: QShaderNodePort::Output, portName: "texCoord" ) |
587 | }); |
588 | const auto lightIntensity = createNode(ports: { |
589 | createPort(portDirection: QShaderNodePort::Output, portName: "lightIntensity" ) |
590 | }); |
591 | const auto exposure = createNode(ports: { |
592 | createPort(portDirection: QShaderNodePort::Output, portName: "exposure" ) |
593 | }); |
594 | const auto fragColor = createNode(ports: { |
595 | createPort(portDirection: QShaderNodePort::Input, portName: "fragColor" ) |
596 | }); |
597 | const auto sampleTexture = createNode(ports: { |
598 | createPort(portDirection: QShaderNodePort::Input, portName: "sampler" ), |
599 | createPort(portDirection: QShaderNodePort::Input, portName: "coord" ), |
600 | createPort(portDirection: QShaderNodePort::Output, portName: "color" ) |
601 | }); |
602 | const auto lightFunction = createNode(ports: { |
603 | createPort(portDirection: QShaderNodePort::Input, portName: "baseColor" ), |
604 | createPort(portDirection: QShaderNodePort::Input, portName: "position" ), |
605 | createPort(portDirection: QShaderNodePort::Input, portName: "lightIntensity" ), |
606 | createPort(portDirection: QShaderNodePort::Output, portName: "outputColor" ) |
607 | }); |
608 | const auto exposureFunction = createNode(ports: { |
609 | createPort(portDirection: QShaderNodePort::Input, portName: "inputColor" ), |
610 | createPort(portDirection: QShaderNodePort::Input, portName: "exposure" ), |
611 | createPort(portDirection: QShaderNodePort::Output, portName: "outputColor" ) |
612 | }); |
613 | |
614 | const auto graph = [=] { |
615 | auto res = QShaderGraph(); |
616 | |
617 | res.addNode(node: worldPosition); |
618 | res.addNode(node: texture); |
619 | res.addNode(node: texCoord); |
620 | res.addNode(node: lightIntensity); |
621 | res.addNode(node: exposure); |
622 | res.addNode(node: fragColor); |
623 | res.addNode(node: sampleTexture); |
624 | res.addNode(node: lightFunction); |
625 | res.addNode(node: exposureFunction); |
626 | |
627 | res.addEdge(edge: createEdge(sourceUuid: texture.uuid(), sourceName: "texture" , targetUuid: sampleTexture.uuid(), targetName: "sampler" )); |
628 | res.addEdge(edge: createEdge(sourceUuid: texCoord.uuid(), sourceName: "texCoord" , targetUuid: sampleTexture.uuid(), targetName: "coord" )); |
629 | |
630 | res.addEdge(edge: createEdge(sourceUuid: worldPosition.uuid(), sourceName: "worldPosition" , targetUuid: lightFunction.uuid(), targetName: "position" )); |
631 | res.addEdge(edge: createEdge(sourceUuid: sampleTexture.uuid(), sourceName: "color" , targetUuid: lightFunction.uuid(), targetName: "baseColor" )); |
632 | res.addEdge(edge: createEdge(sourceUuid: lightIntensity.uuid(), sourceName: "lightIntensity" , targetUuid: lightFunction.uuid(), targetName: "lightIntensity" )); |
633 | |
634 | res.addEdge(edge: createEdge(sourceUuid: lightFunction.uuid(), sourceName: "outputColor" , targetUuid: exposureFunction.uuid(), targetName: "inputColor" )); |
635 | res.addEdge(edge: createEdge(sourceUuid: exposure.uuid(), sourceName: "exposure" , targetUuid: exposureFunction.uuid(), targetName: "exposure" )); |
636 | |
637 | res.addEdge(edge: createEdge(sourceUuid: exposureFunction.uuid(), sourceName: "outputColor" , targetUuid: fragColor.uuid(), targetName: "fragColor" )); |
638 | |
639 | return res; |
640 | }(); |
641 | |
642 | // WHEN |
643 | const auto statements = graph.createStatements(); |
644 | |
645 | // THEN |
646 | const auto expected = QVector<QShaderGraph::Statement>() |
647 | << createStatement(node: texCoord, inputs: {}, outputs: {2}) |
648 | << createStatement(node: texture, inputs: {}, outputs: {1}) |
649 | << createStatement(node: lightIntensity, inputs: {}, outputs: {3}) |
650 | << createStatement(node: sampleTexture, inputs: {1, 2}, outputs: {5}) |
651 | << createStatement(node: worldPosition, inputs: {}, outputs: {0}) |
652 | << createStatement(node: exposure, inputs: {}, outputs: {4}) |
653 | << createStatement(node: lightFunction, inputs: {5, 0, 3}, outputs: {6}) |
654 | << createStatement(node: exposureFunction, inputs: {6, 4}, outputs: {7}) |
655 | << createStatement(node: fragColor, inputs: {7}, outputs: {}); |
656 | dumpStatementsIfNeeded(statements, expected); |
657 | QCOMPARE(statements, expected); |
658 | } |
659 | |
660 | void tst_QShaderGraph::shouldGenerateDifferentStatementsDependingOnActiveLayers() |
661 | { |
662 | // GIVEN |
663 | const auto texCoord = createNode(ports: { |
664 | createPort(portDirection: QShaderNodePort::Output, portName: "texCoord" ) |
665 | }, layers: { |
666 | "diffuseTexture" , |
667 | "normalTexture" |
668 | }); |
669 | const auto diffuseUniform = createNode(ports: { |
670 | createPort(portDirection: QShaderNodePort::Output, portName: "color" ) |
671 | }, layers: {"diffuseUniform" }); |
672 | const auto diffuseTexture = createNode(ports: { |
673 | createPort(portDirection: QShaderNodePort::Input, portName: "coord" ), |
674 | createPort(portDirection: QShaderNodePort::Output, portName: "color" ) |
675 | }, layers: {"diffuseTexture" }); |
676 | const auto normalUniform = createNode(ports: { |
677 | createPort(portDirection: QShaderNodePort::Output, portName: "normal" ) |
678 | }, layers: {"normalUniform" }); |
679 | const auto normalTexture = createNode(ports: { |
680 | createPort(portDirection: QShaderNodePort::Input, portName: "coord" ), |
681 | createPort(portDirection: QShaderNodePort::Output, portName: "normal" ) |
682 | }, layers: {"normalTexture" }); |
683 | const auto lightFunction = createNode(ports: { |
684 | createPort(portDirection: QShaderNodePort::Input, portName: "color" ), |
685 | createPort(portDirection: QShaderNodePort::Input, portName: "normal" ), |
686 | createPort(portDirection: QShaderNodePort::Output, portName: "output" ) |
687 | }); |
688 | const auto fragColor = createNode(ports: { |
689 | createPort(portDirection: QShaderNodePort::Input, portName: "fragColor" ) |
690 | }); |
691 | |
692 | const auto graph = [=] { |
693 | auto res = QShaderGraph(); |
694 | |
695 | res.addNode(node: texCoord); |
696 | res.addNode(node: diffuseUniform); |
697 | res.addNode(node: diffuseTexture); |
698 | res.addNode(node: normalUniform); |
699 | res.addNode(node: normalTexture); |
700 | res.addNode(node: lightFunction); |
701 | res.addNode(node: fragColor); |
702 | |
703 | res.addEdge(edge: createEdge(sourceUuid: diffuseUniform.uuid(), sourceName: "color" , targetUuid: lightFunction.uuid(), targetName: "color" , layers: {"diffuseUniform" })); |
704 | res.addEdge(edge: createEdge(sourceUuid: texCoord.uuid(), sourceName: "texCoord" , targetUuid: diffuseTexture.uuid(), targetName: "coord" , layers: {"diffuseTexture" })); |
705 | res.addEdge(edge: createEdge(sourceUuid: diffuseTexture.uuid(), sourceName: "color" , targetUuid: lightFunction.uuid(), targetName: "color" , layers: {"diffuseTexture" })); |
706 | |
707 | res.addEdge(edge: createEdge(sourceUuid: normalUniform.uuid(), sourceName: "normal" , targetUuid: lightFunction.uuid(), targetName: "normal" , layers: {"normalUniform" })); |
708 | res.addEdge(edge: createEdge(sourceUuid: texCoord.uuid(), sourceName: "texCoord" , targetUuid: normalTexture.uuid(), targetName: "coord" , layers: {"normalTexture" })); |
709 | res.addEdge(edge: createEdge(sourceUuid: normalTexture.uuid(), sourceName: "normal" , targetUuid: lightFunction.uuid(), targetName: "normal" , layers: {"normalTexture" })); |
710 | |
711 | res.addEdge(edge: createEdge(sourceUuid: lightFunction.uuid(), sourceName: "output" , targetUuid: fragColor.uuid(), targetName: "fragColor" )); |
712 | |
713 | return res; |
714 | }(); |
715 | |
716 | { |
717 | // WHEN |
718 | const auto statements = graph.createStatements(enabledLayers: {"diffuseUniform" , "normalUniform" }); |
719 | |
720 | // THEN |
721 | const auto expected = QVector<QShaderGraph::Statement>() |
722 | << createStatement(node: normalUniform, inputs: {}, outputs: {1}) |
723 | << createStatement(node: diffuseUniform, inputs: {}, outputs: {0}) |
724 | << createStatement(node: lightFunction, inputs: {0, 1}, outputs: {2}) |
725 | << createStatement(node: fragColor, inputs: {2}, outputs: {}); |
726 | dumpStatementsIfNeeded(statements, expected); |
727 | QCOMPARE(statements, expected); |
728 | } |
729 | |
730 | { |
731 | // WHEN |
732 | const auto statements = graph.createStatements(enabledLayers: {"diffuseUniform" , "normalTexture" }); |
733 | |
734 | // THEN |
735 | const auto expected = QVector<QShaderGraph::Statement>() |
736 | << createStatement(node: texCoord, inputs: {}, outputs: {0}) |
737 | << createStatement(node: normalTexture, inputs: {0}, outputs: {2}) |
738 | << createStatement(node: diffuseUniform, inputs: {}, outputs: {1}) |
739 | << createStatement(node: lightFunction, inputs: {1, 2}, outputs: {3}) |
740 | << createStatement(node: fragColor, inputs: {3}, outputs: {}); |
741 | dumpStatementsIfNeeded(statements, expected); |
742 | QCOMPARE(statements, expected); |
743 | } |
744 | |
745 | { |
746 | // WHEN |
747 | const auto statements = graph.createStatements(enabledLayers: {"diffuseTexture" , "normalUniform" }); |
748 | |
749 | // THEN |
750 | const auto expected = QVector<QShaderGraph::Statement>() |
751 | << createStatement(node: texCoord, inputs: {}, outputs: {0}) |
752 | << createStatement(node: normalUniform, inputs: {}, outputs: {2}) |
753 | << createStatement(node: diffuseTexture, inputs: {0}, outputs: {1}) |
754 | << createStatement(node: lightFunction, inputs: {1, 2}, outputs: {3}) |
755 | << createStatement(node: fragColor, inputs: {3}, outputs: {}); |
756 | dumpStatementsIfNeeded(statements, expected); |
757 | QCOMPARE(statements, expected); |
758 | } |
759 | |
760 | { |
761 | // WHEN |
762 | const auto statements = graph.createStatements(enabledLayers: {"diffuseTexture" , "normalTexture" }); |
763 | |
764 | // THEN |
765 | const auto expected = QVector<QShaderGraph::Statement>() |
766 | << createStatement(node: texCoord, inputs: {}, outputs: {0}) |
767 | << createStatement(node: normalTexture, inputs: {0}, outputs: {2}) |
768 | << createStatement(node: diffuseTexture, inputs: {0}, outputs: {1}) |
769 | << createStatement(node: lightFunction, inputs: {1, 2}, outputs: {3}) |
770 | << createStatement(node: fragColor, inputs: {3}, outputs: {}); |
771 | dumpStatementsIfNeeded(statements, expected); |
772 | QCOMPARE(statements, expected); |
773 | } |
774 | } |
775 | |
776 | void tst_QShaderGraph::shouldDealWithBranchesWithoutOutput() |
777 | { |
778 | // GIVEN |
779 | const auto input = createNode(ports: { |
780 | createPort(portDirection: QShaderNodePort::Output, portName: "input" ) |
781 | }); |
782 | const auto output = createNode(ports: { |
783 | createPort(portDirection: QShaderNodePort::Input, portName: "output" ) |
784 | }); |
785 | const auto danglingFunction = createNode(ports: { |
786 | createPort(portDirection: QShaderNodePort::Input, portName: "functionInput" ), |
787 | createPort(portDirection: QShaderNodePort::Output, portName: "unbound" ) |
788 | }); |
789 | const auto function = createNode(ports: { |
790 | createPort(portDirection: QShaderNodePort::Input, portName: "functionInput" ), |
791 | createPort(portDirection: QShaderNodePort::Output, portName: "functionOutput" ) |
792 | }); |
793 | |
794 | const auto graph = [=] { |
795 | auto res = QShaderGraph(); |
796 | res.addNode(node: input); |
797 | res.addNode(node: function); |
798 | res.addNode(node: danglingFunction); |
799 | res.addNode(node: output); |
800 | res.addEdge(edge: createEdge(sourceUuid: input.uuid(), sourceName: "input" , targetUuid: function.uuid(), targetName: "functionInput" )); |
801 | res.addEdge(edge: createEdge(sourceUuid: input.uuid(), sourceName: "input" , targetUuid: danglingFunction.uuid(), targetName: "functionInput" )); |
802 | res.addEdge(edge: createEdge(sourceUuid: function.uuid(), sourceName: "functionOutput" , targetUuid: output.uuid(), targetName: "output" )); |
803 | return res; |
804 | }(); |
805 | |
806 | // WHEN |
807 | const auto statements = graph.createStatements(); |
808 | |
809 | // THEN |
810 | // Note that no edge leads to the unbound input |
811 | const auto expected = QVector<QShaderGraph::Statement>() |
812 | << createStatement(node: input, inputs: {}, outputs: {0}) |
813 | << createStatement(node: function, inputs: {0}, outputs: {1}) |
814 | << createStatement(node: output, inputs: {1}, outputs: {}) |
815 | << createStatement(node: danglingFunction, inputs: {0}, outputs: {2}); |
816 | dumpStatementsIfNeeded(statements, expected); |
817 | QCOMPARE(statements, expected); |
818 | } |
819 | |
820 | void tst_QShaderGraph::shouldDiscardEdgesConnectedToDiscardedNodes() |
821 | { |
822 | // GIVEN |
823 | const auto input = createNode(ports: { |
824 | createPort(portDirection: QShaderNodePort::Output, portName: "input" ) |
825 | }); |
826 | const auto output = createNode(ports: { |
827 | createPort(portDirection: QShaderNodePort::Input, portName: "output" ) |
828 | }); |
829 | const auto function0 = createNode(ports: { |
830 | createPort(portDirection: QShaderNodePort::Input, portName: "function0Input" ), |
831 | createPort(portDirection: QShaderNodePort::Output, portName: "function0Output" ) |
832 | }, layers: {"0" }); |
833 | const auto function1 = createNode(ports: { |
834 | createPort(portDirection: QShaderNodePort::Input, portName: "function1Input" ), |
835 | createPort(portDirection: QShaderNodePort::Output, portName: "function1Output" ) |
836 | }, layers: {"1" }); |
837 | |
838 | const auto graph = [=] { |
839 | auto res = QShaderGraph(); |
840 | res.addNode(node: input); |
841 | res.addNode(node: function0); |
842 | res.addNode(node: function1); |
843 | res.addNode(node: output); |
844 | res.addEdge(edge: createEdge(sourceUuid: input.uuid(), sourceName: "input" , targetUuid: function0.uuid(), targetName: "function0Input" , layers: {"0" })); |
845 | res.addEdge(edge: createEdge(sourceUuid: input.uuid(), sourceName: "input" , targetUuid: function1.uuid(), targetName: "function1Input" )); |
846 | res.addEdge(edge: createEdge(sourceUuid: function0.uuid(), sourceName: "function0Output" , targetUuid: output.uuid(), targetName: "output" )); |
847 | res.addEdge(edge: createEdge(sourceUuid: function1.uuid(), sourceName: "function1Output" , targetUuid: output.uuid(), targetName: "output" )); |
848 | return res; |
849 | }(); |
850 | |
851 | // WHEN |
852 | const auto statements = graph.createStatements(enabledLayers: {"0" }); |
853 | |
854 | // THEN |
855 | const auto expected = QVector<QShaderGraph::Statement>() |
856 | << createStatement(node: input, inputs: {}, outputs: {0}) |
857 | << createStatement(node: function0, inputs: {0}, outputs: {1}) |
858 | << createStatement(node: output, inputs: {1}, outputs: {}); |
859 | dumpStatementsIfNeeded(statements, expected); |
860 | QCOMPARE(statements, expected); |
861 | } |
862 | |
863 | QTEST_MAIN(tst_QShaderGraph) |
864 | |
865 | #include "tst_qshadergraph.moc" |
866 | |