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