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