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/qshadernodesloader_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 NodeHash = QHash<QString, QShaderNode>; |
43 | Q_DECLARE_METATYPE(NodeHash); |
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 | const QStringList &extensions = QStringList(), |
58 | const QString &vendor = QString()) |
59 | { |
60 | auto format = QShaderFormat(); |
61 | format.setApi(api); |
62 | format.setVersion(QVersionNumber(majorVersion, minorVersion)); |
63 | format.setExtensions(extensions); |
64 | format.setVendor(vendor); |
65 | return format; |
66 | } |
67 | |
68 | QShaderNodePort createPort(QShaderNodePort::Direction portDirection, const QString &portName) |
69 | { |
70 | auto port = QShaderNodePort(); |
71 | port.direction = portDirection; |
72 | port.name = portName; |
73 | return port; |
74 | } |
75 | |
76 | QShaderNode createNode(const QVector<QShaderNodePort> &ports) |
77 | { |
78 | auto node = QShaderNode(); |
79 | for (const auto &port : ports) |
80 | node.addPort(port); |
81 | return node; |
82 | } |
83 | } |
84 | |
85 | class tst_QShaderNodesLoader : public QObject |
86 | { |
87 | Q_OBJECT |
88 | private slots: |
89 | void shouldManipulateLoaderMembers(); |
90 | void shouldLoadFromJsonStream_data(); |
91 | void shouldLoadFromJsonStream(); |
92 | }; |
93 | |
94 | void tst_QShaderNodesLoader::shouldManipulateLoaderMembers() |
95 | { |
96 | // GIVEN |
97 | auto loader = QShaderNodesLoader(); |
98 | |
99 | // THEN (default state) |
100 | QCOMPARE(loader.status(), QShaderNodesLoader::Null); |
101 | QVERIFY(!loader.device()); |
102 | QVERIFY(loader.nodes().isEmpty()); |
103 | |
104 | // WHEN |
105 | auto device1 = createBuffer(data: QByteArray("..........." ), openMode: QIODevice::NotOpen); |
106 | loader.setDevice(device1.data()); |
107 | |
108 | // THEN |
109 | QCOMPARE(loader.status(), QShaderNodesLoader::Error); |
110 | QCOMPARE(loader.device(), device1.data()); |
111 | QVERIFY(loader.nodes().isEmpty()); |
112 | |
113 | // WHEN |
114 | auto device2 = createBuffer(data: QByteArray("..........." ), openMode: QIODevice::ReadOnly); |
115 | loader.setDevice(device2.data()); |
116 | |
117 | // THEN |
118 | QCOMPARE(loader.status(), QShaderNodesLoader::Waiting); |
119 | QCOMPARE(loader.device(), device2.data()); |
120 | QVERIFY(loader.nodes().isEmpty()); |
121 | } |
122 | |
123 | void tst_QShaderNodesLoader::shouldLoadFromJsonStream_data() |
124 | { |
125 | QTest::addColumn<QBufferPointer>(name: "device" ); |
126 | QTest::addColumn<NodeHash>(name: "nodes" ); |
127 | QTest::addColumn<QShaderNodesLoader::Status>(name: "status" ); |
128 | |
129 | QTest::newRow(dataTag: "empty" ) << createBuffer(data: "" , openMode: QIODevice::ReadOnly) << NodeHash() << QShaderNodesLoader::Error; |
130 | |
131 | const auto smallJson = "{" |
132 | " \"inputValue\": {" |
133 | " \"outputs\": [" |
134 | " \"value\"" |
135 | " ]," |
136 | " \"parameters\": {" |
137 | " \"name\": \"defaultName\"," |
138 | " \"qualifier\": {" |
139 | " \"type\": \"QShaderLanguage::StorageQualifier\"," |
140 | " \"value\": \"QShaderLanguage::Uniform\"" |
141 | " }," |
142 | " \"type\": {" |
143 | " \"type\": \"QShaderLanguage::VariableType\"," |
144 | " \"value\": \"QShaderLanguage::Vec3\"" |
145 | " }," |
146 | " \"defaultValue\": {" |
147 | " \"type\": \"float\"," |
148 | " \"value\": \"1.25\"" |
149 | " }" |
150 | " }," |
151 | " \"rules\": [" |
152 | " {" |
153 | " \"format\": {" |
154 | " \"api\": \"OpenGLES\"," |
155 | " \"major\": 2," |
156 | " \"minor\": 0" |
157 | " }," |
158 | " \"substitution\": \"highp vec3 $value = $name;\"," |
159 | " \"headerSnippets\": [ \"varying highp vec3 $name;\" ]" |
160 | " }," |
161 | " {" |
162 | " \"format\": {" |
163 | " \"api\": \"OpenGLCompatibilityProfile\"," |
164 | " \"major\": 2," |
165 | " \"minor\": 1" |
166 | " }," |
167 | " \"substitution\": \"vec3 $value = $name;\"," |
168 | " \"headerSnippets\": [ \"in vec3 $name;\" ]" |
169 | " }" |
170 | " ]" |
171 | " }," |
172 | " \"fragColor\": {" |
173 | " \"inputs\": [" |
174 | " \"fragColor\"" |
175 | " ]," |
176 | " \"rules\": [" |
177 | " {" |
178 | " \"format\": {" |
179 | " \"api\": \"OpenGLES\"," |
180 | " \"major\": 2," |
181 | " \"minor\": 0" |
182 | " }," |
183 | " \"substitution\": \"gl_fragColor = $fragColor;\"" |
184 | " }," |
185 | " {" |
186 | " \"format\": {" |
187 | " \"api\": \"OpenGLNoProfile\"," |
188 | " \"major\": 4," |
189 | " \"minor\": 0" |
190 | " }," |
191 | " \"substitution\": \"fragColor = $fragColor;\"," |
192 | " \"headerSnippets\": [ \"out vec4 fragColor;\" ]" |
193 | " }" |
194 | " ]" |
195 | " }," |
196 | " \"lightModel\": {" |
197 | " \"inputs\": [" |
198 | " \"baseColor\"," |
199 | " \"position\"," |
200 | " \"lightIntensity\"" |
201 | " ]," |
202 | " \"outputs\": [" |
203 | " \"outputColor\"" |
204 | " ]," |
205 | " \"rules\": [" |
206 | " {" |
207 | " \"format\": {" |
208 | " \"api\": \"OpenGLES\"," |
209 | " \"major\": 2," |
210 | " \"minor\": 0," |
211 | " \"extensions\": [ \"ext1\", \"ext2\" ]," |
212 | " \"vendor\": \"kdab\"" |
213 | " }," |
214 | " \"substitution\": \"highp vec4 $outputColor = lightModel($baseColor, $position, $lightIntensity);\"," |
215 | " \"headerSnippets\": [ \"#pragma include es2/lightmodel.frag.inc\" ]" |
216 | " }," |
217 | " {" |
218 | " \"format\": {" |
219 | " \"api\": \"OpenGLCoreProfile\"," |
220 | " \"major\": 3," |
221 | " \"minor\": 3" |
222 | " }," |
223 | " \"substitution\": \"vec4 $outputColor = lightModel($baseColor, $position, $lightIntensity);\"," |
224 | " \"headerSnippets\": [ \"#pragma include gl3/lightmodel.frag.inc\" ]" |
225 | " }" |
226 | " ]" |
227 | " }" |
228 | "}" ; |
229 | |
230 | const auto smallProtos = [this]{ |
231 | const auto openGLES2 = createFormat(api: QShaderFormat::OpenGLES, majorVersion: 2, minorVersion: 0); |
232 | const auto openGLES2Extended = createFormat(api: QShaderFormat::OpenGLES, majorVersion: 2, minorVersion: 0, extensions: {"ext1" , "ext2" }, vendor: "kdab" ); |
233 | const auto openGL2 = createFormat(api: QShaderFormat::OpenGLCompatibilityProfile, majorVersion: 2, minorVersion: 1); |
234 | const auto openGL3 = createFormat(api: QShaderFormat::OpenGLCoreProfile, majorVersion: 3, minorVersion: 3); |
235 | const auto openGL4 = createFormat(api: QShaderFormat::OpenGLNoProfile, majorVersion: 4, minorVersion: 0); |
236 | |
237 | auto protos = NodeHash(); |
238 | |
239 | auto inputValue = createNode(ports: { |
240 | createPort(portDirection: QShaderNodePort::Output, portName: "value" ) |
241 | }); |
242 | inputValue.setParameter(name: "name" , value: "defaultName" ); |
243 | inputValue.setParameter(name: "qualifier" , value: QVariant::fromValue<QShaderLanguage::StorageQualifier>(value: QShaderLanguage::Uniform)); |
244 | inputValue.setParameter(name: "type" , value: QVariant::fromValue<QShaderLanguage::VariableType>(value: QShaderLanguage::Vec3)); |
245 | inputValue.setParameter(name: "defaultValue" , value: QVariant(1.25f)); |
246 | inputValue.addRule(format: openGLES2, rule: QShaderNode::Rule("highp vec3 $value = $name;" , |
247 | QByteArrayList() << "varying highp vec3 $name;" )); |
248 | inputValue.addRule(format: openGL2, rule: QShaderNode::Rule("vec3 $value = $name;" , |
249 | QByteArrayList() << "in vec3 $name;" )); |
250 | protos.insert(akey: "inputValue" , avalue: inputValue); |
251 | |
252 | auto fragColor = createNode(ports: { |
253 | createPort(portDirection: QShaderNodePort::Input, portName: "fragColor" ) |
254 | }); |
255 | fragColor.addRule(format: openGLES2, rule: QShaderNode::Rule("gl_fragColor = $fragColor;" )); |
256 | fragColor.addRule(format: openGL4, rule: QShaderNode::Rule("fragColor = $fragColor;" , |
257 | QByteArrayList() << "out vec4 fragColor;" )); |
258 | protos.insert(QStringLiteral("fragColor" ), avalue: fragColor); |
259 | |
260 | auto lightModel = createNode(ports: { |
261 | createPort(portDirection: QShaderNodePort::Input, portName: "baseColor" ), |
262 | createPort(portDirection: QShaderNodePort::Input, portName: "position" ), |
263 | createPort(portDirection: QShaderNodePort::Input, portName: "lightIntensity" ), |
264 | createPort(portDirection: QShaderNodePort::Output, portName: "outputColor" ) |
265 | }); |
266 | lightModel.addRule(format: openGLES2Extended, rule: QShaderNode::Rule("highp vec4 $outputColor = lightModel($baseColor, $position, $lightIntensity);" , |
267 | QByteArrayList() << "#pragma include es2/lightmodel.frag.inc" )); |
268 | lightModel.addRule(format: openGL3, rule: QShaderNode::Rule("vec4 $outputColor = lightModel($baseColor, $position, $lightIntensity);" , |
269 | QByteArrayList() << "#pragma include gl3/lightmodel.frag.inc" )); |
270 | protos.insert(akey: "lightModel" , avalue: lightModel); |
271 | |
272 | return protos; |
273 | }(); |
274 | |
275 | QTest::newRow(dataTag: "NotOpen" ) << createBuffer(data: smallJson, openMode: QIODevice::NotOpen) << NodeHash() << QShaderNodesLoader::Error; |
276 | QTest::newRow(dataTag: "CorrectJSON" ) << createBuffer(data: smallJson) << smallProtos << QShaderNodesLoader::Ready; |
277 | } |
278 | |
279 | void tst_QShaderNodesLoader::shouldLoadFromJsonStream() |
280 | { |
281 | // GIVEN |
282 | QFETCH(QBufferPointer, device); |
283 | |
284 | auto loader = QShaderNodesLoader(); |
285 | |
286 | // WHEN |
287 | loader.setDevice(device.data()); |
288 | loader.load(); |
289 | |
290 | // THEN |
291 | QFETCH(QShaderNodesLoader::Status, status); |
292 | QCOMPARE(loader.status(), status); |
293 | |
294 | QFETCH(NodeHash, nodes); |
295 | const auto sortedKeys = [](const NodeHash &nodes) { |
296 | auto res = nodes.keys(); |
297 | res.sort(); |
298 | return res; |
299 | }; |
300 | const auto sortedParameters = [](const QShaderNode &node) { |
301 | auto res = node.parameterNames(); |
302 | res.sort(); |
303 | return res; |
304 | }; |
305 | QCOMPARE(sortedKeys(loader.nodes()), sortedKeys(nodes)); |
306 | for (const auto &key : nodes.keys()) { |
307 | const auto actual = loader.nodes().value(akey: key); |
308 | const auto expected = nodes.value(akey: key); |
309 | |
310 | QVERIFY(actual.uuid().isNull()); |
311 | QCOMPARE(actual.ports(), expected.ports()); |
312 | QCOMPARE(sortedParameters(actual), sortedParameters(expected)); |
313 | for (const auto &name : expected.parameterNames()) { |
314 | QCOMPARE(actual.parameter(name), expected.parameter(name)); |
315 | } |
316 | QCOMPARE(actual.availableFormats(), expected.availableFormats()); |
317 | for (const auto &format : expected.availableFormats()) { |
318 | QCOMPARE(actual.rule(format), expected.rule(format)); |
319 | } |
320 | } |
321 | } |
322 | |
323 | QTEST_MAIN(tst_QShaderNodesLoader) |
324 | |
325 | #include "tst_qshadernodesloader.moc" |
326 | |