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