1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2015 Klaralvdalens Datakonsult AB (KDAB). |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the Qt3D module 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 | #include <QtTest/QTest> |
30 | #include <Qt3DCore/private/qnode_p.h> |
31 | #include <Qt3DCore/private/qscene_p.h> |
32 | #include <Qt3DRender/private/qrenderstate_p.h> |
33 | #include <Qt3DCore/private/qnodecreatedchangegenerator_p.h> |
34 | |
35 | #include <Qt3DRender/QEffect> |
36 | #include <Qt3DRender/QMaterial> |
37 | #include <Qt3DRender/QParameter> |
38 | #include <Qt3DRender/QTechnique> |
39 | #include <Qt3DRender/QRenderPass> |
40 | #include <Qt3DExtras/QPhongMaterial> |
41 | #include <Qt3DExtras/QDiffuseMapMaterial> |
42 | #include <Qt3DExtras/QPerVertexColorMaterial> |
43 | #include <Qt3DExtras/QNormalDiffuseMapMaterial> |
44 | #include <Qt3DExtras/QDiffuseSpecularMapMaterial> |
45 | #include <Qt3DExtras/QNormalDiffuseMapAlphaMaterial> |
46 | #include <Qt3DExtras/QNormalDiffuseSpecularMapMaterial> |
47 | |
48 | #include <Qt3DRender/private/qmaterial_p.h> |
49 | |
50 | #include "testpostmanarbiter.h" |
51 | |
52 | class TestMaterial : public Qt3DRender::QMaterial |
53 | { |
54 | public: |
55 | explicit TestMaterial(Qt3DCore::QNode *parent = 0) |
56 | : Qt3DRender::QMaterial(parent) |
57 | , m_effect(new Qt3DRender::QEffect(this)) |
58 | , m_technique(new Qt3DRender::QTechnique(m_effect)) |
59 | , m_renderPass(new Qt3DRender::QRenderPass(m_technique)) |
60 | , m_shaderProgram(new Qt3DRender::QShaderProgram(m_renderPass)) |
61 | { |
62 | m_renderPass->setShaderProgram(m_shaderProgram); |
63 | m_technique->addRenderPass(pass: m_renderPass); |
64 | m_effect->addTechnique(t: m_technique); |
65 | setEffect(m_effect); |
66 | } |
67 | |
68 | Qt3DRender::QEffect *m_effect; |
69 | Qt3DRender::QTechnique *m_technique; |
70 | Qt3DRender::QRenderPass *m_renderPass; |
71 | Qt3DRender::QShaderProgram *m_shaderProgram; |
72 | }; |
73 | |
74 | class tst_QMaterial : public QObject |
75 | { |
76 | Q_OBJECT |
77 | public: |
78 | tst_QMaterial() |
79 | : QObject() |
80 | { |
81 | qRegisterMetaType<Qt3DCore::QNode*>(); |
82 | qRegisterMetaType<Qt3DRender::QEffect*>(typeName: "Qt3DRender::QEffect*" ); |
83 | } |
84 | |
85 | private: |
86 | |
87 | void compareEffects(const Qt3DRender::QEffect *original, const Qt3DRender::QEffect *clone) |
88 | { |
89 | bool isEffectNull = (original == nullptr); |
90 | if (isEffectNull) { |
91 | QVERIFY(!clone); |
92 | } else { |
93 | QVERIFY(clone); |
94 | QCOMPARE(original->id(), clone->id()); |
95 | |
96 | compareParameters(original: original->parameters(), clone: clone->parameters()); |
97 | |
98 | const int techniqueCounts = original->techniques().size(); |
99 | QCOMPARE(techniqueCounts, clone->techniques().size()); |
100 | |
101 | for (int i = 0; i < techniqueCounts; ++i) |
102 | compareTechniques(original: original->techniques().at(i), clone: clone->techniques().at(i)); |
103 | } |
104 | } |
105 | |
106 | void compareTechniques(const Qt3DRender::QTechnique *original, const Qt3DRender::QTechnique *clone) |
107 | { |
108 | QCOMPARE(original->id(), clone->id()); |
109 | |
110 | compareParameters(original: original->parameters(), clone: clone->parameters()); |
111 | |
112 | const int passesCount = original->renderPasses().size(); |
113 | QCOMPARE(passesCount, clone->renderPasses().size()); |
114 | |
115 | for (int i = 0; i < passesCount; ++i) |
116 | compareRenderPasses(original: original->renderPasses().at(i), clone: clone->renderPasses().at(i)); |
117 | } |
118 | |
119 | void compareRenderPasses(const Qt3DRender::QRenderPass *original, const Qt3DRender::QRenderPass *clone) |
120 | { |
121 | QCOMPARE(original->id(), clone->id()); |
122 | |
123 | compareParameters(original: original->parameters(), clone: clone->parameters()); |
124 | compareRenderStates(original: original->renderStates(), clone: clone->renderStates()); |
125 | compareFilterKeys(original: original->filterKeys(), clone: clone->filterKeys()); |
126 | compareShaderPrograms(original: original->shaderProgram(), clone: clone->shaderProgram()); |
127 | } |
128 | |
129 | void compareParameters(const Qt3DRender::ParameterList &original, const Qt3DRender::ParameterList &clone) |
130 | { |
131 | QCOMPARE(original.size(), clone.size()); |
132 | const int parametersCount = original.size(); |
133 | for (int i = 0; i < parametersCount; ++i) { |
134 | const Qt3DRender::QParameter *originParam = original.at(i); |
135 | const Qt3DRender::QParameter *cloneParam = clone.at(i); |
136 | QCOMPARE(originParam->id(), cloneParam->id()); |
137 | QCOMPARE(cloneParam->name(), originParam->name()); |
138 | QCOMPARE(cloneParam->value(), originParam->value()); |
139 | } |
140 | } |
141 | |
142 | void compareFilterKeys(const QVector<Qt3DRender::QFilterKey *> &original, const QVector<Qt3DRender::QFilterKey *> &clone) |
143 | { |
144 | const int annotationsCount = original.size(); |
145 | QCOMPARE(annotationsCount, clone.size()); |
146 | |
147 | for (int i = 0; i < annotationsCount; ++i) { |
148 | const Qt3DRender::QFilterKey *origAnnotation = original.at(i); |
149 | const Qt3DRender::QFilterKey *cloneAnnotation = clone.at(i); |
150 | QCOMPARE(origAnnotation->id(), cloneAnnotation->id()); |
151 | QCOMPARE(origAnnotation->name(), cloneAnnotation->name()); |
152 | QCOMPARE(origAnnotation->value(), cloneAnnotation->value()); |
153 | } |
154 | } |
155 | |
156 | void compareRenderStates(const QVector<Qt3DRender::QRenderState *> &original, const QVector<Qt3DRender::QRenderState *> &clone) |
157 | { |
158 | const int renderStatesCount = original.size(); |
159 | QCOMPARE(renderStatesCount, clone.size()); |
160 | |
161 | for (int i = 0; i < renderStatesCount; ++i) { |
162 | Qt3DRender::QRenderState *originState = original.at(i); |
163 | Qt3DRender::QRenderState *cloneState = clone.at(i); |
164 | QCOMPARE(originState->id(), originState->id()); |
165 | QVERIFY(Qt3DRender::QRenderStatePrivate::get(originState)->m_type == Qt3DRender::QRenderStatePrivate::get(cloneState)->m_type); |
166 | } |
167 | } |
168 | |
169 | void compareShaderPrograms(const Qt3DRender::QShaderProgram *original, const Qt3DRender::QShaderProgram *clone) |
170 | { |
171 | bool isOriginalNull = (original == nullptr); |
172 | if (isOriginalNull) { |
173 | QVERIFY(!clone); |
174 | } else { |
175 | QVERIFY(clone); |
176 | QCOMPARE(original->id(), clone->id()); |
177 | QVERIFY(original->vertexShaderCode() == clone->vertexShaderCode()); |
178 | QVERIFY(original->fragmentShaderCode() == clone->fragmentShaderCode()); |
179 | QVERIFY(original->geometryShaderCode() == clone->geometryShaderCode()); |
180 | QVERIFY(original->computeShaderCode() == clone->computeShaderCode()); |
181 | QVERIFY(original->tessellationControlShaderCode() == clone->tessellationControlShaderCode()); |
182 | QVERIFY(original->tessellationEvaluationShaderCode() == clone->tessellationEvaluationShaderCode()); |
183 | } |
184 | } |
185 | |
186 | private Q_SLOTS: |
187 | |
188 | void checkCloning_data() |
189 | { |
190 | QTest::addColumn<Qt3DRender::QMaterial *>(name: "material" ); |
191 | |
192 | Qt3DRender::QMaterial *material = new Qt3DRender::QMaterial(); |
193 | QTest::newRow(dataTag: "empty material" ) << material; |
194 | material = new TestMaterial(); |
195 | QTest::newRow(dataTag: "test material" ) << material; |
196 | material = new Qt3DExtras::QPhongMaterial(); |
197 | QTest::newRow(dataTag: "QPhongMaterial" ) << material; |
198 | material = new Qt3DExtras::QDiffuseMapMaterial(); |
199 | QTest::newRow(dataTag: "QDiffuseMapMaterial" ) << material; |
200 | material = new Qt3DExtras::QDiffuseSpecularMapMaterial(); |
201 | QTest::newRow(dataTag: "QDiffuseMapSpecularMaterial" ) << material; |
202 | material = new Qt3DExtras::QPerVertexColorMaterial(); |
203 | QTest::newRow(dataTag: "QPerVertexColorMaterial" ) << material; |
204 | material = new Qt3DExtras::QNormalDiffuseMapMaterial(); |
205 | QTest::newRow(dataTag: "QNormalDiffuseMapMaterial" ) << material; |
206 | material = new Qt3DExtras::QNormalDiffuseMapAlphaMaterial(); |
207 | QTest::newRow(dataTag: "QNormalDiffuseMapAlphaMaterial" ) << material; |
208 | material = new Qt3DExtras::QNormalDiffuseSpecularMapMaterial(); |
209 | QTest::newRow(dataTag: "QNormalDiffuseSpecularMapMaterial" ) << material; |
210 | } |
211 | |
212 | void checkCloning() |
213 | { |
214 | // GIVEN |
215 | QFETCH(Qt3DRender::QMaterial *, material); |
216 | |
217 | // WHEN |
218 | Qt3DCore::QNodeCreatedChangeGenerator creationChangeGenerator(material); |
219 | QVector<Qt3DCore::QNodeCreatedChangeBasePtr> creationChanges = creationChangeGenerator.creationChanges(); |
220 | |
221 | // THEN |
222 | QVERIFY(creationChanges.size() >= 1); |
223 | |
224 | const Qt3DCore::QNodeCreatedChangePtr<Qt3DRender::QMaterialData> creationChangeData = |
225 | qSharedPointerCast<Qt3DCore::QNodeCreatedChange<Qt3DRender::QMaterialData>>(src: creationChanges.first()); |
226 | const Qt3DRender::QMaterialData &cloneData = creationChangeData->data; |
227 | |
228 | // THEN |
229 | QCOMPARE(material->id(), creationChangeData->subjectId()); |
230 | QCOMPARE(material->isEnabled(), creationChangeData->isNodeEnabled()); |
231 | QCOMPARE(material->metaObject(), creationChangeData->metaObject()); |
232 | QCOMPARE(material->effect() ? material->effect()->id() : Qt3DCore::QNodeId(), cloneData.effectId); |
233 | QCOMPARE(material->parameters().size(), cloneData.parameterIds.size()); |
234 | |
235 | for (int i = 0, m = material->parameters().size(); i < m; ++i) |
236 | QCOMPARE(material->parameters().at(i)->id(), cloneData.parameterIds.at(i)); |
237 | |
238 | // TO DO: Add unit tests for parameter and effect that do check this |
239 | // compareParameters(material->parameters(), clone->parameters()); |
240 | // compareEffects(material->effect(), clone->effect()); |
241 | } |
242 | |
243 | void checkEffectUpdate() |
244 | { |
245 | // GIVEN |
246 | TestArbiter arbiter; |
247 | QScopedPointer<Qt3DRender::QMaterial> material(new Qt3DRender::QMaterial()); |
248 | arbiter.setArbiterOnNode(material.data()); |
249 | |
250 | // WHEN |
251 | Qt3DRender::QEffect effect; |
252 | material->setEffect(&effect); |
253 | |
254 | // THEN |
255 | QCOMPARE(arbiter.dirtyNodes.size(), 1); |
256 | QCOMPARE(arbiter.dirtyNodes.front(), material.data()); |
257 | |
258 | arbiter.dirtyNodes.clear(); |
259 | |
260 | // GIVEN |
261 | TestArbiter arbiter2; |
262 | QScopedPointer<TestMaterial> material2(new TestMaterial()); |
263 | arbiter2.setArbiterOnNode(material2.data()); |
264 | |
265 | // WHEN |
266 | material2->setEffect(&effect); |
267 | |
268 | // THEN |
269 | QCOMPARE(arbiter2.dirtyNodes.size(), 1); |
270 | QCOMPARE(arbiter2.dirtyNodes.front(), material2.data()); |
271 | |
272 | arbiter2.dirtyNodes.clear(); |
273 | } |
274 | |
275 | void checkDynamicParametersAddedUpdates() |
276 | { |
277 | // GIVEN |
278 | TestArbiter arbiter; |
279 | TestMaterial *material = new TestMaterial(); |
280 | arbiter.setArbiterOnNode(material); |
281 | |
282 | QCoreApplication::processEvents(); |
283 | // Clear events trigger by child generation of TestMnterial |
284 | arbiter.events.clear(); |
285 | |
286 | // WHEN (add parameter to material) |
287 | Qt3DRender::QParameter *param = new Qt3DRender::QParameter("testParamMaterial" , QVariant::fromValue(value: 383.0f)); |
288 | material->addParameter(parameter: param); |
289 | QCoreApplication::processEvents(); |
290 | |
291 | // THEN |
292 | QCOMPARE(param->parent(), material); |
293 | |
294 | // THEN |
295 | QCOMPARE(arbiter.events.size(), 0); |
296 | QCOMPARE(arbiter.dirtyNodes.size(), 1); |
297 | QVERIFY(material->parameters().contains(param)); |
298 | |
299 | // WHEN (add parameter to effect) |
300 | param = new Qt3DRender::QParameter("testParamEffect" , QVariant::fromValue(value: 383.0f)); |
301 | material->effect()->addParameter(parameter: param); |
302 | QCoreApplication::processEvents(); |
303 | |
304 | // THEN |
305 | QCOMPARE(arbiter.events.size(), 0); |
306 | QCOMPARE(arbiter.dirtyNodes.size(), 2); |
307 | QVERIFY(material->effect()->parameters().contains(param)); |
308 | |
309 | // WHEN (add parameter to technique) |
310 | param = new Qt3DRender::QParameter("testParamTechnique" , QVariant::fromValue(value: 383.0f)); |
311 | material->m_technique->addParameter(p: param); |
312 | QCoreApplication::processEvents(); |
313 | |
314 | // THEN |
315 | QCOMPARE(arbiter.events.size(), 0); |
316 | QCOMPARE(arbiter.dirtyNodes.size(), 3); |
317 | QVERIFY(material->m_technique->parameters().contains(param)); |
318 | |
319 | |
320 | // WHEN (add parameter to renderpass) |
321 | param = new Qt3DRender::QParameter("testParamRenderPass" , QVariant::fromValue(value: 383.0f)); |
322 | material->m_renderPass->addParameter(p: param); |
323 | QCoreApplication::processEvents(); |
324 | |
325 | // THEN |
326 | QCOMPARE(arbiter.events.size(), 0); |
327 | QCOMPARE(arbiter.dirtyNodes.size(), 4); |
328 | QVERIFY(material->m_renderPass->parameters().contains(param)); |
329 | } |
330 | |
331 | void checkShaderProgramUpdates() |
332 | { |
333 | // GIVEN |
334 | TestArbiter arbiter; |
335 | TestMaterial *material = new TestMaterial(); |
336 | arbiter.setArbiterOnNode(material); |
337 | |
338 | // WHEN |
339 | const QByteArray vertexCode = QByteArrayLiteral("new vertex shader code" ); |
340 | material->m_shaderProgram->setVertexShaderCode(vertexCode); |
341 | |
342 | // THEN |
343 | QCOMPARE(arbiter.dirtyNodes.size(), 1); |
344 | QCOMPARE(arbiter.dirtyNodes.front(), material->m_shaderProgram); |
345 | |
346 | arbiter.dirtyNodes.clear(); |
347 | |
348 | // WHEN |
349 | const QByteArray fragmentCode = QByteArrayLiteral("new fragment shader code" ); |
350 | material->m_shaderProgram->setFragmentShaderCode(fragmentCode); |
351 | |
352 | // THEN |
353 | QCOMPARE(arbiter.dirtyNodes.size(), 1); |
354 | QCOMPARE(arbiter.dirtyNodes.front(), material->m_shaderProgram); |
355 | |
356 | arbiter.dirtyNodes.clear(); |
357 | // WHEN |
358 | const QByteArray geometryCode = QByteArrayLiteral("new geometry shader code" ); |
359 | material->m_shaderProgram->setGeometryShaderCode(geometryCode); |
360 | |
361 | // THEN |
362 | QCOMPARE(arbiter.dirtyNodes.size(), 1); |
363 | QCOMPARE(arbiter.dirtyNodes.front(), material->m_shaderProgram); |
364 | |
365 | arbiter.dirtyNodes.clear(); |
366 | |
367 | // WHEN |
368 | const QByteArray computeCode = QByteArrayLiteral("new compute shader code" ); |
369 | material->m_shaderProgram->setComputeShaderCode(computeCode); |
370 | |
371 | // THEN |
372 | QCOMPARE(arbiter.dirtyNodes.size(), 1); |
373 | QCOMPARE(arbiter.dirtyNodes.front(), material->m_shaderProgram); |
374 | |
375 | arbiter.dirtyNodes.clear(); |
376 | |
377 | // WHEN |
378 | const QByteArray tesselControlCode = QByteArrayLiteral("new tessellation control shader code" ); |
379 | material->m_shaderProgram->setTessellationControlShaderCode(tesselControlCode); |
380 | |
381 | // THEN |
382 | QCOMPARE(arbiter.dirtyNodes.size(), 1); |
383 | QCOMPARE(arbiter.dirtyNodes.front(), material->m_shaderProgram); |
384 | |
385 | arbiter.dirtyNodes.clear(); |
386 | |
387 | // WHEN |
388 | const QByteArray tesselEvalCode = QByteArrayLiteral("new tessellation eval shader code" ); |
389 | material->m_shaderProgram->setTessellationEvaluationShaderCode(tesselEvalCode); |
390 | |
391 | // THEN |
392 | QCOMPARE(arbiter.dirtyNodes.size(), 1); |
393 | QCOMPARE(arbiter.dirtyNodes.front(), material->m_shaderProgram); |
394 | |
395 | arbiter.dirtyNodes.clear(); |
396 | } |
397 | |
398 | void checkEffectBookkeeping() |
399 | { |
400 | // GIVEN |
401 | QScopedPointer<Qt3DRender::QMaterial> material(new Qt3DRender::QMaterial); |
402 | { |
403 | // WHEN |
404 | Qt3DRender::QEffect effect; |
405 | material->setEffect(&effect); |
406 | |
407 | // THEN |
408 | QCOMPARE(effect.parent(), material.data()); |
409 | QCOMPARE(material->effect(), &effect); |
410 | } |
411 | // THEN (Should not crash and effect be unset) |
412 | QVERIFY(material->effect() == nullptr); |
413 | |
414 | { |
415 | // WHEN |
416 | Qt3DRender::QMaterial someOtherMaterial; |
417 | QScopedPointer<Qt3DRender::QEffect> effect(new Qt3DRender::QEffect(&someOtherMaterial)); |
418 | material->setEffect(effect.data()); |
419 | |
420 | // THEN |
421 | QCOMPARE(effect->parent(), &someOtherMaterial); |
422 | QCOMPARE(material->effect(), effect.data()); |
423 | |
424 | // WHEN |
425 | material.reset(); |
426 | effect.reset(); |
427 | |
428 | // THEN Should not crash when the effect is destroyed (tests for failed removal of destruction helper) |
429 | } |
430 | } |
431 | |
432 | void checkParametersBookkeeping() |
433 | { |
434 | // GIVEN |
435 | QScopedPointer<Qt3DRender::QMaterial> material(new Qt3DRender::QMaterial); |
436 | { |
437 | // WHEN |
438 | Qt3DRender::QParameter param; |
439 | material->addParameter(parameter: ¶m); |
440 | |
441 | // THEN |
442 | QCOMPARE(param.parent(), material.data()); |
443 | QCOMPARE(material->parameters().size(), 1); |
444 | } |
445 | // THEN (Should not crash and parameter be unset) |
446 | QVERIFY(material->parameters().empty()); |
447 | |
448 | { |
449 | // WHEN |
450 | Qt3DRender::QMaterial someOtherMaterial; |
451 | QScopedPointer<Qt3DRender::QParameter> param(new Qt3DRender::QParameter(&someOtherMaterial)); |
452 | material->addParameter(parameter: param.data()); |
453 | |
454 | // THEN |
455 | QCOMPARE(param->parent(), &someOtherMaterial); |
456 | QCOMPARE(material->parameters().size(), 1); |
457 | |
458 | // WHEN |
459 | material.reset(); |
460 | param.reset(); |
461 | |
462 | // THEN Should not crash when the parameter is destroyed (tests for failed removal of destruction helper) |
463 | } |
464 | } |
465 | }; |
466 | |
467 | QTEST_MAIN(tst_QMaterial) |
468 | |
469 | #include "tst_qmaterial.moc" |
470 | |