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 <QtCore/qbuffer.h> |
33 | |
34 | #include <QtGui/private/qshadergraphloader_p.h> |
35 | #include <QtGui/private/qshaderlanguage_p.h> |
36 | |
37 | using QBufferPointer = QSharedPointer<QBuffer>; |
38 | Q_DECLARE_METATYPE(QBufferPointer); |
39 | |
40 | using PrototypeHash = QHash<QString, QShaderNode>; |
41 | Q_DECLARE_METATYPE(PrototypeHash); |
42 | |
43 | namespace |
44 | { |
45 | QBufferPointer createBuffer(const QByteArray &data, QIODevice::OpenMode openMode = QIODevice::ReadOnly) |
46 | { |
47 | auto buffer = QBufferPointer::create(); |
48 | buffer->setData(data); |
49 | if (openMode != QIODevice::NotOpen) |
50 | buffer->open(openMode); |
51 | return buffer; |
52 | } |
53 | |
54 | QShaderFormat createFormat(QShaderFormat::Api api, int majorVersion, int minorVersion) |
55 | { |
56 | auto format = QShaderFormat(); |
57 | format.setApi(api); |
58 | format.setVersion(QVersionNumber(majorVersion, minorVersion)); |
59 | return format; |
60 | } |
61 | |
62 | QShaderNodePort createPort(QShaderNodePort::Direction portDirection, const QString &portName) |
63 | { |
64 | auto port = QShaderNodePort(); |
65 | port.direction = portDirection; |
66 | port.name = portName; |
67 | return port; |
68 | } |
69 | |
70 | QShaderNode createNode(const QVector<QShaderNodePort> &ports, const QStringList &layers = QStringList()) |
71 | { |
72 | auto node = QShaderNode(); |
73 | node.setUuid(QUuid::createUuid()); |
74 | node.setLayers(layers); |
75 | for (const auto &port : ports) |
76 | node.addPort(port); |
77 | return node; |
78 | } |
79 | |
80 | QShaderGraph::Edge createEdge(const QUuid &sourceUuid, const QString &sourceName, |
81 | const QUuid &targetUuid, const QString &targetName, |
82 | const QStringList &layers = QStringList()) |
83 | { |
84 | auto edge = QShaderGraph::Edge(); |
85 | edge.sourceNodeUuid = sourceUuid; |
86 | edge.sourcePortName = sourceName; |
87 | edge.targetNodeUuid = targetUuid; |
88 | edge.targetPortName = targetName; |
89 | edge.layers = layers; |
90 | return edge; |
91 | } |
92 | |
93 | QShaderGraph createGraph() |
94 | { |
95 | const auto openGLES2 = createFormat(api: QShaderFormat::OpenGLES, majorVersion: 2, minorVersion: 0); |
96 | const auto openGL3 = createFormat(api: QShaderFormat::OpenGLCoreProfile, majorVersion: 3, minorVersion: 0); |
97 | |
98 | auto graph = QShaderGraph(); |
99 | |
100 | auto worldPosition = createNode(ports: { |
101 | createPort(portDirection: QShaderNodePort::Output, portName: "value" ) |
102 | }); |
103 | worldPosition.setUuid(QUuid("{00000000-0000-0000-0000-000000000001}" )); |
104 | worldPosition.setParameter(name: "name" , value: "worldPosition" ); |
105 | worldPosition.setParameter(name: "qualifier" , value: QVariant::fromValue<QShaderLanguage::StorageQualifier>(value: QShaderLanguage::Input)); |
106 | worldPosition.setParameter(name: "type" , value: QVariant::fromValue<QShaderLanguage::VariableType>(value: QShaderLanguage::Vec3)); |
107 | worldPosition.addRule(format: openGLES2, rule: QShaderNode::Rule("highp $type $value = $name;" , |
108 | QByteArrayList() << "$qualifier highp $type $name;" )); |
109 | worldPosition.addRule(format: openGL3, rule: QShaderNode::Rule("$type $value = $name;" , |
110 | QByteArrayList() << "$qualifier $type $name;" )); |
111 | |
112 | auto texture = createNode(ports: { |
113 | createPort(portDirection: QShaderNodePort::Output, portName: "texture" ) |
114 | }); |
115 | texture.setUuid(QUuid("{00000000-0000-0000-0000-000000000002}" )); |
116 | texture.addRule(format: openGLES2, rule: QShaderNode::Rule("sampler2D $texture = texture;" , |
117 | QByteArrayList() << "uniform sampler2D texture;" )); |
118 | texture.addRule(format: openGL3, rule: QShaderNode::Rule("sampler2D $texture = texture;" , |
119 | QByteArrayList() << "uniform sampler2D texture;" )); |
120 | |
121 | auto texCoord = createNode(ports: { |
122 | createPort(portDirection: QShaderNodePort::Output, portName: "texCoord" ) |
123 | }); |
124 | texCoord.setUuid(QUuid("{00000000-0000-0000-0000-000000000003}" )); |
125 | texCoord.addRule(format: openGLES2, rule: QShaderNode::Rule("highp vec2 $texCoord = texCoord;" , |
126 | QByteArrayList() << "varying highp vec2 texCoord;" )); |
127 | texCoord.addRule(format: openGL3, rule: QShaderNode::Rule("vec2 $texCoord = texCoord;" , |
128 | QByteArrayList() << "in vec2 texCoord;" )); |
129 | |
130 | auto lightIntensity = createNode(ports: { |
131 | createPort(portDirection: QShaderNodePort::Output, portName: "value" ) |
132 | }); |
133 | lightIntensity.setUuid(QUuid("{00000000-0000-0000-0000-000000000004}" )); |
134 | lightIntensity.setParameter(name: "name" , value: "defaultName" ); |
135 | lightIntensity.setParameter(name: "qualifier" , value: QVariant::fromValue<QShaderLanguage::StorageQualifier>(value: QShaderLanguage::Uniform)); |
136 | lightIntensity.setParameter(name: "type" , value: QVariant::fromValue<QShaderLanguage::VariableType>(value: QShaderLanguage::Float)); |
137 | lightIntensity.addRule(format: openGLES2, rule: QShaderNode::Rule("highp $type $value = $name;" , |
138 | QByteArrayList() << "$qualifier highp $type $name;" )); |
139 | lightIntensity.addRule(format: openGL3, rule: QShaderNode::Rule("$type $value = $name;" , |
140 | QByteArrayList() << "$qualifier $type $name;" )); |
141 | |
142 | auto exposure = createNode(ports: { |
143 | createPort(portDirection: QShaderNodePort::Output, portName: "exposure" ) |
144 | }); |
145 | exposure.setUuid(QUuid("{00000000-0000-0000-0000-000000000005}" )); |
146 | exposure.addRule(format: openGLES2, rule: QShaderNode::Rule("highp float $exposure = exposure;" , |
147 | QByteArrayList() << "uniform highp float exposure;" )); |
148 | exposure.addRule(format: openGL3, rule: QShaderNode::Rule("float $exposure = exposure;" , |
149 | QByteArrayList() << "uniform float exposure;" )); |
150 | |
151 | auto fragColor = createNode(ports: { |
152 | createPort(portDirection: QShaderNodePort::Input, portName: "fragColor" ) |
153 | }); |
154 | fragColor.setUuid(QUuid("{00000000-0000-0000-0000-000000000006}" )); |
155 | fragColor.addRule(format: openGLES2, rule: QShaderNode::Rule("gl_fragColor = $fragColor;" )); |
156 | fragColor.addRule(format: openGL3, rule: QShaderNode::Rule("fragColor = $fragColor;" , |
157 | QByteArrayList() << "out vec4 fragColor;" )); |
158 | |
159 | auto sampleTexture = createNode(ports: { |
160 | createPort(portDirection: QShaderNodePort::Input, portName: "sampler" ), |
161 | createPort(portDirection: QShaderNodePort::Input, portName: "coord" ), |
162 | createPort(portDirection: QShaderNodePort::Output, portName: "color" ) |
163 | }); |
164 | sampleTexture.setUuid(QUuid("{00000000-0000-0000-0000-000000000007}" )); |
165 | sampleTexture.addRule(format: openGLES2, rule: QShaderNode::Rule("highp vec4 $color = texture2D($sampler, $coord);" )); |
166 | sampleTexture.addRule(format: openGL3, rule: QShaderNode::Rule("vec4 $color = texture2D($sampler, $coord);" )); |
167 | |
168 | auto lightFunction = createNode(ports: { |
169 | createPort(portDirection: QShaderNodePort::Input, portName: "baseColor" ), |
170 | createPort(portDirection: QShaderNodePort::Input, portName: "position" ), |
171 | createPort(portDirection: QShaderNodePort::Input, portName: "lightIntensity" ), |
172 | createPort(portDirection: QShaderNodePort::Output, portName: "outputColor" ) |
173 | }); |
174 | lightFunction.setUuid(QUuid("{00000000-0000-0000-0000-000000000008}" )); |
175 | lightFunction.addRule(format: openGLES2, rule: QShaderNode::Rule("highp vec4 $outputColor = lightModel($baseColor, $position, $lightIntensity);" , |
176 | QByteArrayList() << "#pragma include es2/lightmodel.frag.inc" )); |
177 | lightFunction.addRule(format: openGL3, rule: QShaderNode::Rule("vec4 $outputColor = lightModel($baseColor, $position, $lightIntensity);" , |
178 | QByteArrayList() << "#pragma include gl3/lightmodel.frag.inc" )); |
179 | |
180 | auto exposureFunction = createNode(ports: { |
181 | createPort(portDirection: QShaderNodePort::Input, portName: "inputColor" ), |
182 | createPort(portDirection: QShaderNodePort::Input, portName: "exposure" ), |
183 | createPort(portDirection: QShaderNodePort::Output, portName: "outputColor" ) |
184 | }); |
185 | exposureFunction.setUuid(QUuid("{00000000-0000-0000-0000-000000000009}" )); |
186 | exposureFunction.addRule(format: openGLES2, rule: QShaderNode::Rule("highp vec4 $outputColor = $inputColor * pow(2.0, $exposure);" )); |
187 | exposureFunction.addRule(format: openGL3, rule: QShaderNode::Rule("vec4 $outputColor = $inputColor * pow(2.0, $exposure);" )); |
188 | |
189 | graph.addNode(node: worldPosition); |
190 | graph.addNode(node: texture); |
191 | graph.addNode(node: texCoord); |
192 | graph.addNode(node: lightIntensity); |
193 | graph.addNode(node: exposure); |
194 | graph.addNode(node: fragColor); |
195 | graph.addNode(node: sampleTexture); |
196 | graph.addNode(node: lightFunction); |
197 | graph.addNode(node: exposureFunction); |
198 | |
199 | graph.addEdge(edge: createEdge(sourceUuid: texture.uuid(), sourceName: "texture" , targetUuid: sampleTexture.uuid(), targetName: "sampler" )); |
200 | graph.addEdge(edge: createEdge(sourceUuid: texCoord.uuid(), sourceName: "texCoord" , targetUuid: sampleTexture.uuid(), targetName: "coord" )); |
201 | |
202 | graph.addEdge(edge: createEdge(sourceUuid: worldPosition.uuid(), sourceName: "value" , targetUuid: lightFunction.uuid(), targetName: "position" )); |
203 | graph.addEdge(edge: createEdge(sourceUuid: sampleTexture.uuid(), sourceName: "color" , targetUuid: lightFunction.uuid(), targetName: "baseColor" )); |
204 | graph.addEdge(edge: createEdge(sourceUuid: lightIntensity.uuid(), sourceName: "value" , targetUuid: lightFunction.uuid(), targetName: "lightIntensity" )); |
205 | |
206 | graph.addEdge(edge: createEdge(sourceUuid: lightFunction.uuid(), sourceName: "outputColor" , targetUuid: exposureFunction.uuid(), targetName: "inputColor" )); |
207 | graph.addEdge(edge: createEdge(sourceUuid: exposure.uuid(), sourceName: "exposure" , targetUuid: exposureFunction.uuid(), targetName: "exposure" )); |
208 | |
209 | graph.addEdge(edge: createEdge(sourceUuid: exposureFunction.uuid(), sourceName: "outputColor" , targetUuid: fragColor.uuid(), targetName: "fragColor" )); |
210 | |
211 | return graph; |
212 | } |
213 | |
214 | void debugStatement(const QString &prefix, const QShaderGraph::Statement &statement) |
215 | { |
216 | qDebug() << prefix << statement.inputs << statement.uuid().toString() << statement.outputs; |
217 | } |
218 | |
219 | void dumpStatementsIfNeeded(const QVector<QShaderGraph::Statement> &statements, const QVector<QShaderGraph::Statement> &expected) |
220 | { |
221 | if (statements != expected) { |
222 | for (int i = 0; i < qMax(a: statements.size(), b: expected.size()); i++) { |
223 | qDebug() << "----" << i << "----" ; |
224 | if (i < statements.size()) |
225 | debugStatement(prefix: "A:" , statement: statements.at(i)); |
226 | if (i < expected.size()) |
227 | debugStatement(prefix: "E:" , statement: expected.at(i)); |
228 | qDebug() << "-----------" ; |
229 | } |
230 | } |
231 | } |
232 | } |
233 | |
234 | class tst_QShaderGraphLoader : public QObject |
235 | { |
236 | Q_OBJECT |
237 | private slots: |
238 | void shouldManipulateLoaderMembers(); |
239 | void shouldLoadFromJsonStream_data(); |
240 | void shouldLoadFromJsonStream(); |
241 | }; |
242 | |
243 | void tst_QShaderGraphLoader::shouldManipulateLoaderMembers() |
244 | { |
245 | // GIVEN |
246 | auto loader = QShaderGraphLoader(); |
247 | |
248 | // THEN (default state) |
249 | QCOMPARE(loader.status(), QShaderGraphLoader::Null); |
250 | QVERIFY(!loader.device()); |
251 | QVERIFY(loader.graph().nodes().isEmpty()); |
252 | QVERIFY(loader.graph().edges().isEmpty()); |
253 | QVERIFY(loader.prototypes().isEmpty()); |
254 | |
255 | // WHEN |
256 | auto device1 = createBuffer(data: QByteArray("..........." ), openMode: QIODevice::NotOpen); |
257 | loader.setDevice(device1.data()); |
258 | |
259 | // THEN |
260 | QCOMPARE(loader.status(), QShaderGraphLoader::Error); |
261 | QCOMPARE(loader.device(), device1.data()); |
262 | QVERIFY(loader.graph().nodes().isEmpty()); |
263 | QVERIFY(loader.graph().edges().isEmpty()); |
264 | |
265 | // WHEN |
266 | auto device2 = createBuffer(data: QByteArray("..........." ), openMode: QIODevice::ReadOnly); |
267 | loader.setDevice(device2.data()); |
268 | |
269 | // THEN |
270 | QCOMPARE(loader.status(), QShaderGraphLoader::Waiting); |
271 | QCOMPARE(loader.device(), device2.data()); |
272 | QVERIFY(loader.graph().nodes().isEmpty()); |
273 | QVERIFY(loader.graph().edges().isEmpty()); |
274 | |
275 | |
276 | // WHEN |
277 | const auto prototypes = [this]{ |
278 | auto res = QHash<QString, QShaderNode>(); |
279 | res.insert(akey: "foo" , avalue: createNode(ports: {})); |
280 | return res; |
281 | }(); |
282 | loader.setPrototypes(prototypes); |
283 | |
284 | // THEN |
285 | QCOMPARE(loader.prototypes().size(), prototypes.size()); |
286 | QVERIFY(loader.prototypes().contains("foo" )); |
287 | QCOMPARE(loader.prototypes().value("foo" ).uuid(), prototypes.value("foo" ).uuid()); |
288 | } |
289 | |
290 | void tst_QShaderGraphLoader::shouldLoadFromJsonStream_data() |
291 | { |
292 | QTest::addColumn<QBufferPointer>(name: "device" ); |
293 | QTest::addColumn<PrototypeHash>(name: "prototypes" ); |
294 | QTest::addColumn<QShaderGraph>(name: "graph" ); |
295 | QTest::addColumn<QShaderGraphLoader::Status>(name: "status" ); |
296 | |
297 | QTest::newRow(dataTag: "empty" ) << createBuffer(data: "" , openMode: QIODevice::ReadOnly) << PrototypeHash() |
298 | << QShaderGraph() << QShaderGraphLoader::Error; |
299 | |
300 | const auto smallJson = "{" |
301 | " \"nodes\": [" |
302 | " {" |
303 | " \"uuid\": \"{00000000-0000-0000-0000-000000000001}\"," |
304 | " \"type\": \"MyInput\"," |
305 | " \"layers\": [\"foo\", \"bar\"]" |
306 | " }," |
307 | " {" |
308 | " \"uuid\": \"{00000000-0000-0000-0000-000000000002}\"," |
309 | " \"type\": \"MyOutput\"" |
310 | " }," |
311 | " {" |
312 | " \"uuid\": \"{00000000-0000-0000-0000-000000000003}\"," |
313 | " \"type\": \"MyFunction\"" |
314 | " }" |
315 | " ]," |
316 | " \"edges\": [" |
317 | " {" |
318 | " \"sourceUuid\": \"{00000000-0000-0000-0000-000000000001}\"," |
319 | " \"sourcePort\": \"input\"," |
320 | " \"targetUuid\": \"{00000000-0000-0000-0000-000000000003}\"," |
321 | " \"targetPort\": \"functionInput\"," |
322 | " \"layers\": [\"bar\", \"baz\"]" |
323 | " }," |
324 | " {" |
325 | " \"sourceUuid\": \"{00000000-0000-0000-0000-000000000003}\"," |
326 | " \"sourcePort\": \"functionOutput\"," |
327 | " \"targetUuid\": \"{00000000-0000-0000-0000-000000000002}\"," |
328 | " \"targetPort\": \"output\"" |
329 | " }" |
330 | " ]" |
331 | "}" ; |
332 | |
333 | const auto smallProtos = [this]{ |
334 | auto protos = PrototypeHash(); |
335 | |
336 | auto input = createNode(ports: { |
337 | createPort(portDirection: QShaderNodePort::Output, portName: "input" ) |
338 | }); |
339 | protos.insert(akey: "MyInput" , avalue: input); |
340 | |
341 | auto output = createNode(ports: { |
342 | createPort(portDirection: QShaderNodePort::Input, portName: "output" ) |
343 | }); |
344 | protos.insert(akey: "MyOutput" , avalue: output); |
345 | |
346 | auto function = createNode(ports: { |
347 | createPort(portDirection: QShaderNodePort::Input, portName: "functionInput" ), |
348 | createPort(portDirection: QShaderNodePort::Output, portName: "functionOutput" ) |
349 | }); |
350 | protos.insert(akey: "MyFunction" , avalue: function); |
351 | return protos; |
352 | }(); |
353 | |
354 | const auto smallGraph = [this]{ |
355 | auto graph = QShaderGraph(); |
356 | |
357 | auto input = createNode(ports: { |
358 | createPort(portDirection: QShaderNodePort::Output, portName: "input" ) |
359 | }, layers: {"foo" , "bar" }); |
360 | input.setUuid(QUuid("{00000000-0000-0000-0000-000000000001}" )); |
361 | auto output = createNode(ports: { |
362 | createPort(portDirection: QShaderNodePort::Input, portName: "output" ) |
363 | }); |
364 | output.setUuid(QUuid("{00000000-0000-0000-0000-000000000002}" )); |
365 | auto function = createNode(ports: { |
366 | createPort(portDirection: QShaderNodePort::Input, portName: "functionInput" ), |
367 | createPort(portDirection: QShaderNodePort::Output, portName: "functionOutput" ) |
368 | }); |
369 | function.setUuid(QUuid("{00000000-0000-0000-0000-000000000003}" )); |
370 | |
371 | graph.addNode(node: input); |
372 | graph.addNode(node: output); |
373 | graph.addNode(node: function); |
374 | graph.addEdge(edge: createEdge(sourceUuid: input.uuid(), sourceName: "input" , targetUuid: function.uuid(), targetName: "functionInput" , layers: {"bar" , "baz" })); |
375 | graph.addEdge(edge: createEdge(sourceUuid: function.uuid(), sourceName: "functionOutput" , targetUuid: output.uuid(), targetName: "output" )); |
376 | |
377 | return graph; |
378 | }(); |
379 | |
380 | QTest::newRow(dataTag: "TwoNodesOneEdge" ) << createBuffer(data: smallJson) << smallProtos << smallGraph << QShaderGraphLoader::Ready; |
381 | QTest::newRow(dataTag: "NotOpen" ) << createBuffer(data: smallJson, openMode: QIODevice::NotOpen) << smallProtos << QShaderGraph() << QShaderGraphLoader::Error; |
382 | QTest::newRow(dataTag: "NoPrototype" ) << createBuffer(data: smallJson) << PrototypeHash() << QShaderGraph() << QShaderGraphLoader::Error; |
383 | |
384 | const auto complexJson = "{" |
385 | " \"nodes\": [" |
386 | " {" |
387 | " \"uuid\": \"{00000000-0000-0000-0000-000000000001}\"," |
388 | " \"type\": \"inputValue\"," |
389 | " \"parameters\": {" |
390 | " \"name\": \"worldPosition\"," |
391 | " \"qualifier\": {" |
392 | " \"type\": \"QShaderLanguage::StorageQualifier\"," |
393 | " \"value\": \"QShaderLanguage::Input\"" |
394 | " }," |
395 | " \"type\": {" |
396 | " \"type\": \"QShaderLanguage::VariableType\"," |
397 | " \"value\": \"QShaderLanguage::Vec3\"" |
398 | " }" |
399 | " }" |
400 | " }," |
401 | " {" |
402 | " \"uuid\": \"{00000000-0000-0000-0000-000000000002}\"," |
403 | " \"type\": \"texture\"" |
404 | " }," |
405 | " {" |
406 | " \"uuid\": \"{00000000-0000-0000-0000-000000000003}\"," |
407 | " \"type\": \"texCoord\"" |
408 | " }," |
409 | " {" |
410 | " \"uuid\": \"{00000000-0000-0000-0000-000000000004}\"," |
411 | " \"type\": \"inputValue\"" |
412 | " }," |
413 | " {" |
414 | " \"uuid\": \"{00000000-0000-0000-0000-000000000005}\"," |
415 | " \"type\": \"exposure\"" |
416 | " }," |
417 | " {" |
418 | " \"uuid\": \"{00000000-0000-0000-0000-000000000006}\"," |
419 | " \"type\": \"fragColor\"" |
420 | " }," |
421 | " {" |
422 | " \"uuid\": \"{00000000-0000-0000-0000-000000000007}\"," |
423 | " \"type\": \"sampleTexture\"" |
424 | " }," |
425 | " {" |
426 | " \"uuid\": \"{00000000-0000-0000-0000-000000000008}\"," |
427 | " \"type\": \"lightModel\"" |
428 | " }," |
429 | " {" |
430 | " \"uuid\": \"{00000000-0000-0000-0000-000000000009}\"," |
431 | " \"type\": \"exposureFunction\"" |
432 | " }" |
433 | " ]," |
434 | " \"edges\": [" |
435 | " {" |
436 | " \"sourceUuid\": \"{00000000-0000-0000-0000-000000000002}\"," |
437 | " \"sourcePort\": \"texture\"," |
438 | " \"targetUuid\": \"{00000000-0000-0000-0000-000000000007}\"," |
439 | " \"targetPort\": \"sampler\"" |
440 | " }," |
441 | " {" |
442 | " \"sourceUuid\": \"{00000000-0000-0000-0000-000000000003}\"," |
443 | " \"sourcePort\": \"texCoord\"," |
444 | " \"targetUuid\": \"{00000000-0000-0000-0000-000000000007}\"," |
445 | " \"targetPort\": \"coord\"" |
446 | " }," |
447 | " {" |
448 | " \"sourceUuid\": \"{00000000-0000-0000-0000-000000000001}\"," |
449 | " \"sourcePort\": \"value\"," |
450 | " \"targetUuid\": \"{00000000-0000-0000-0000-000000000008}\"," |
451 | " \"targetPort\": \"position\"" |
452 | " }," |
453 | " {" |
454 | " \"sourceUuid\": \"{00000000-0000-0000-0000-000000000007}\"," |
455 | " \"sourcePort\": \"color\"," |
456 | " \"targetUuid\": \"{00000000-0000-0000-0000-000000000008}\"," |
457 | " \"targetPort\": \"baseColor\"" |
458 | " }," |
459 | " {" |
460 | " \"sourceUuid\": \"{00000000-0000-0000-0000-000000000004}\"," |
461 | " \"sourcePort\": \"value\"," |
462 | " \"targetUuid\": \"{00000000-0000-0000-0000-000000000008}\"," |
463 | " \"targetPort\": \"lightIntensity\"" |
464 | " }," |
465 | " {" |
466 | " \"sourceUuid\": \"{00000000-0000-0000-0000-000000000008}\"," |
467 | " \"sourcePort\": \"outputColor\"," |
468 | " \"targetUuid\": \"{00000000-0000-0000-0000-000000000009}\"," |
469 | " \"targetPort\": \"inputColor\"" |
470 | " }," |
471 | " {" |
472 | " \"sourceUuid\": \"{00000000-0000-0000-0000-000000000005}\"," |
473 | " \"sourcePort\": \"exposure\"," |
474 | " \"targetUuid\": \"{00000000-0000-0000-0000-000000000009}\"," |
475 | " \"targetPort\": \"exposure\"" |
476 | " }," |
477 | " {" |
478 | " \"sourceUuid\": \"{00000000-0000-0000-0000-000000000009}\"," |
479 | " \"sourcePort\": \"outputColor\"," |
480 | " \"targetUuid\": \"{00000000-0000-0000-0000-000000000006}\"," |
481 | " \"targetPort\": \"fragColor\"" |
482 | " }" |
483 | " ]" |
484 | "}" ; |
485 | |
486 | const auto complexProtos = [this]{ |
487 | const auto openGLES2 = createFormat(api: QShaderFormat::OpenGLES, majorVersion: 2, minorVersion: 0); |
488 | const auto openGL3 = createFormat(api: QShaderFormat::OpenGLCoreProfile, majorVersion: 3, minorVersion: 0); |
489 | |
490 | auto protos = PrototypeHash(); |
491 | |
492 | auto inputValue = createNode(ports: { |
493 | createPort(portDirection: QShaderNodePort::Output, portName: "value" ) |
494 | }); |
495 | inputValue.setParameter(name: "name" , value: "defaultName" ); |
496 | inputValue.setParameter(name: "qualifier" , value: QVariant::fromValue<QShaderLanguage::StorageQualifier>(value: QShaderLanguage::Uniform)); |
497 | inputValue.setParameter(name: "type" , value: QVariant::fromValue<QShaderLanguage::VariableType>(value: QShaderLanguage::Float)); |
498 | inputValue.addRule(format: openGLES2, rule: QShaderNode::Rule("highp $type $value = $name;" , |
499 | QByteArrayList() << "$qualifier highp $type $name;" )); |
500 | inputValue.addRule(format: openGL3, rule: QShaderNode::Rule("$type $value = $name;" , |
501 | QByteArrayList() << "$qualifier $type $name;" )); |
502 | protos.insert(akey: "inputValue" , avalue: inputValue); |
503 | |
504 | auto texture = createNode(ports: { |
505 | createPort(portDirection: QShaderNodePort::Output, portName: "texture" ) |
506 | }); |
507 | texture.addRule(format: openGLES2, rule: QShaderNode::Rule("sampler2D $texture = texture;" , |
508 | QByteArrayList() << "uniform sampler2D texture;" )); |
509 | texture.addRule(format: openGL3, rule: QShaderNode::Rule("sampler2D $texture = texture;" , |
510 | QByteArrayList() << "uniform sampler2D texture;" )); |
511 | protos.insert(akey: "texture" , avalue: texture); |
512 | |
513 | auto texCoord = createNode(ports: { |
514 | createPort(portDirection: QShaderNodePort::Output, portName: "texCoord" ) |
515 | }); |
516 | texCoord.addRule(format: openGLES2, rule: QShaderNode::Rule("highp vec2 $texCoord = texCoord;" , |
517 | QByteArrayList() << "varying highp vec2 texCoord;" )); |
518 | texCoord.addRule(format: openGL3, rule: QShaderNode::Rule("vec2 $texCoord = texCoord;" , |
519 | QByteArrayList() << "in vec2 texCoord;" )); |
520 | protos.insert(akey: "texCoord" , avalue: texCoord); |
521 | |
522 | auto exposure = createNode(ports: { |
523 | createPort(portDirection: QShaderNodePort::Output, portName: "exposure" ) |
524 | }); |
525 | exposure.addRule(format: openGLES2, rule: QShaderNode::Rule("highp float $exposure = exposure;" , |
526 | QByteArrayList() << "uniform highp float exposure;" )); |
527 | exposure.addRule(format: openGL3, rule: QShaderNode::Rule("float $exposure = exposure;" , |
528 | QByteArrayList() << "uniform float exposure;" )); |
529 | protos.insert(akey: "exposure" , avalue: exposure); |
530 | |
531 | auto fragColor = createNode(ports: { |
532 | createPort(portDirection: QShaderNodePort::Input, portName: "fragColor" ) |
533 | }); |
534 | fragColor.addRule(format: openGLES2, rule: QShaderNode::Rule("gl_fragColor = $fragColor;" )); |
535 | fragColor.addRule(format: openGL3, rule: QShaderNode::Rule("fragColor = $fragColor;" , |
536 | QByteArrayList() << "out vec4 fragColor;" )); |
537 | protos.insert(akey: "fragColor" , avalue: fragColor); |
538 | |
539 | auto sampleTexture = createNode(ports: { |
540 | createPort(portDirection: QShaderNodePort::Input, portName: "sampler" ), |
541 | createPort(portDirection: QShaderNodePort::Input, portName: "coord" ), |
542 | createPort(portDirection: QShaderNodePort::Output, portName: "color" ) |
543 | }); |
544 | sampleTexture.addRule(format: openGLES2, rule: QShaderNode::Rule("highp vec4 $color = texture2D($sampler, $coord);" )); |
545 | sampleTexture.addRule(format: openGL3, rule: QShaderNode::Rule("vec4 $color = texture2D($sampler, $coord);" )); |
546 | protos.insert(akey: "sampleTexture" , avalue: sampleTexture); |
547 | |
548 | auto lightModel = createNode(ports: { |
549 | createPort(portDirection: QShaderNodePort::Input, portName: "baseColor" ), |
550 | createPort(portDirection: QShaderNodePort::Input, portName: "position" ), |
551 | createPort(portDirection: QShaderNodePort::Input, portName: "lightIntensity" ), |
552 | createPort(portDirection: QShaderNodePort::Output, portName: "outputColor" ) |
553 | }); |
554 | lightModel.addRule(format: openGLES2, rule: QShaderNode::Rule("highp vec4 $outputColor = lightModel($baseColor, $position, $lightIntensity);" , |
555 | QByteArrayList() << "#pragma include es2/lightmodel.frag.inc" )); |
556 | lightModel.addRule(format: openGL3, rule: QShaderNode::Rule("vec4 $outputColor = lightModel($baseColor, $position, $lightIntensity);" , |
557 | QByteArrayList() << "#pragma include gl3/lightmodel.frag.inc" )); |
558 | protos.insert(akey: "lightModel" , avalue: lightModel); |
559 | |
560 | auto exposureFunction = createNode(ports: { |
561 | createPort(portDirection: QShaderNodePort::Input, portName: "inputColor" ), |
562 | createPort(portDirection: QShaderNodePort::Input, portName: "exposure" ), |
563 | createPort(portDirection: QShaderNodePort::Output, portName: "outputColor" ) |
564 | }); |
565 | exposureFunction.addRule(format: openGLES2, rule: QShaderNode::Rule("highp vec4 $outputColor = $inputColor * pow(2.0, $exposure);" )); |
566 | exposureFunction.addRule(format: openGL3, rule: QShaderNode::Rule("vec4 $outputColor = $inputColor * pow(2.0, $exposure);" )); |
567 | protos.insert(akey: "exposureFunction" , avalue: exposureFunction); |
568 | |
569 | return protos; |
570 | }(); |
571 | |
572 | const auto complexGraph = createGraph(); |
573 | |
574 | QTest::newRow(dataTag: "ComplexGraph" ) << createBuffer(data: complexJson) << complexProtos << complexGraph << QShaderGraphLoader::Ready; |
575 | } |
576 | |
577 | void tst_QShaderGraphLoader::shouldLoadFromJsonStream() |
578 | { |
579 | // GIVEN |
580 | QFETCH(QBufferPointer, device); |
581 | QFETCH(PrototypeHash, prototypes); |
582 | |
583 | auto loader = QShaderGraphLoader(); |
584 | |
585 | // WHEN |
586 | loader.setPrototypes(prototypes); |
587 | loader.setDevice(device.data()); |
588 | loader.load(); |
589 | |
590 | // THEN |
591 | QFETCH(QShaderGraphLoader::Status, status); |
592 | QCOMPARE(loader.status(), status); |
593 | |
594 | QFETCH(QShaderGraph, graph); |
595 | const auto statements = loader.graph().createStatements(enabledLayers: {"foo" , "bar" , "baz" }); |
596 | const auto expected = graph.createStatements(enabledLayers: {"foo" , "bar" , "baz" }); |
597 | dumpStatementsIfNeeded(statements, expected); |
598 | QCOMPARE(statements, expected); |
599 | |
600 | const auto sortedParameters = [](const QShaderNode &node) { |
601 | auto res = node.parameterNames(); |
602 | res.sort(); |
603 | return res; |
604 | }; |
605 | |
606 | for (int i = 0; i < statements.size(); i++) { |
607 | const auto actualNode = statements.at(i).node; |
608 | const auto expectedNode = expected.at(i).node; |
609 | |
610 | QCOMPARE(actualNode.layers(), expectedNode.layers()); |
611 | QCOMPARE(actualNode.ports(), expectedNode.ports()); |
612 | QCOMPARE(sortedParameters(actualNode), sortedParameters(expectedNode)); |
613 | for (const auto &name : expectedNode.parameterNames()) { |
614 | QCOMPARE(actualNode.parameter(name), expectedNode.parameter(name)); |
615 | } |
616 | QCOMPARE(actualNode.availableFormats(), expectedNode.availableFormats()); |
617 | for (const auto &format : expectedNode.availableFormats()) { |
618 | QCOMPARE(actualNode.rule(format), expectedNode.rule(format)); |
619 | } |
620 | } |
621 | } |
622 | |
623 | QTEST_MAIN(tst_QShaderGraphLoader) |
624 | |
625 | #include "tst_qshadergraphloader.moc" |
626 | |