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
52class TestMaterial : public Qt3DRender::QMaterial
53{
54public:
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
74class tst_QMaterial : public QObject
75{
76 Q_OBJECT
77public:
78 tst_QMaterial()
79 : QObject()
80 {
81 qRegisterMetaType<Qt3DCore::QNode*>();
82 qRegisterMetaType<Qt3DRender::QEffect*>(typeName: "Qt3DRender::QEffect*");
83 }
84
85private:
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
186private 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: &param);
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
467QTEST_MAIN(tst_QMaterial)
468
469#include "tst_qmaterial.moc"
470

source code of qt3d/tests/auto/render/qmaterial/tst_qmaterial.cpp