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 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 <qbackendnodetester.h>
31#include <Qt3DCore/qpropertyupdatedchange.h>
32#include <Qt3DCore/private/qbackendnode_p.h>
33#include <Qt3DRender/private/shaderbuilder_p.h>
34#include <Qt3DRender/qshaderprogram.h>
35#include <Qt3DRender/qshaderprogrambuilder.h>
36#include "testrenderer.h"
37#include "testpostmanarbiter.h"
38
39#include <QByteArray>
40
41Q_DECLARE_METATYPE(Qt3DRender::QShaderProgram::ShaderType)
42
43class tst_ShaderBuilder : public Qt3DCore::QBackendNodeTester
44{
45 Q_OBJECT
46private slots:
47 void shouldHaveGlobalDefaultPrototypes()
48 {
49 // GIVEN
50
51 // THEN
52 QCOMPARE(Qt3DRender::Render::ShaderBuilder::getPrototypesFile(), QStringLiteral(":/prototypes/default.json"));
53 QVERIFY(!Qt3DRender::Render::ShaderBuilder::getPrototypeNames().isEmpty());
54
55 // WHEN
56 Qt3DRender::Render::ShaderBuilder::setPrototypesFile(":/prototypes.json");
57
58 // THEN
59 QCOMPARE(Qt3DRender::Render::ShaderBuilder::getPrototypesFile(), QStringLiteral(":/prototypes.json"));
60 auto prototypeNames = Qt3DRender::Render::ShaderBuilder::getPrototypeNames();
61 prototypeNames.sort();
62 const auto expectedPrototypeNames = QStringList() << "exposure"
63 << "exposureFunction"
64 << "fragColor"
65 << "lightIntensity"
66 << "lightModel"
67 << "sampleTexture"
68 << "texCoord"
69 << "texture"
70 << "worldPosition";
71 QCOMPARE(prototypeNames, expectedPrototypeNames);
72 }
73
74 void shouldHaveInitialState()
75 {
76 // GIVEN
77 Qt3DRender::Render::ShaderBuilder shaderBuilder;
78
79 // THEN
80 QVERIFY(!shaderBuilder.isEnabled());
81 QVERIFY(shaderBuilder.enabledLayers().isEmpty());
82 for (int i = 0; i <= Qt3DRender::QShaderProgram::Compute; i++) {
83 const auto type = static_cast<Qt3DRender::QShaderProgram::ShaderType>(i);
84 QCOMPARE(shaderBuilder.shaderGraph(type), QUrl());
85 QCOMPARE(shaderBuilder.shaderCode(type), QByteArray());
86 QVERIFY(!shaderBuilder.isShaderCodeDirty(type));
87 }
88 }
89
90 void shouldHavePropertiesMirroringFromItsPeer_data()
91 {
92 QTest::addColumn<Qt3DRender::QShaderProgramBuilder*>(name: "frontend");
93
94 {
95 auto frontend = new Qt3DRender::QShaderProgramBuilder;
96 QTest::newRow(dataTag: "empty") << frontend;
97 }
98 {
99 auto frontend = new Qt3DRender::QShaderProgramBuilder;
100 auto program = new Qt3DRender::QShaderProgram(frontend);
101 frontend->setShaderProgram(program);
102 QTest::newRow(dataTag: "shaderProgram") << frontend;
103 }
104 {
105 auto frontend = new Qt3DRender::QShaderProgramBuilder;
106 frontend->setEnabledLayers({"foo", "bar"});
107 QTest::newRow(dataTag: "enabledLayers") << frontend;
108 }
109 {
110 auto frontend = new Qt3DRender::QShaderProgramBuilder;
111 frontend->setVertexShaderGraph(QUrl::fromEncoded(url: "qrc:/vertex.json"));
112 QTest::newRow(dataTag: "vertex") << frontend;
113 }
114 {
115 auto frontend = new Qt3DRender::QShaderProgramBuilder;
116 frontend->setTessellationControlShaderGraph(QUrl::fromEncoded(url: "qrc:/tesscontrol.json"));
117 QTest::newRow(dataTag: "tessellationControl") << frontend;
118 }
119 {
120 auto frontend = new Qt3DRender::QShaderProgramBuilder;
121 frontend->setTessellationEvaluationShaderGraph(QUrl::fromEncoded(url: "qrc:/tesseval.json"));
122 QTest::newRow(dataTag: "tessellationEvaluation") << frontend;
123 }
124 {
125 auto frontend = new Qt3DRender::QShaderProgramBuilder;
126 frontend->setGeometryShaderGraph(QUrl::fromEncoded(url: "qrc:/geometry.json"));
127 QTest::newRow(dataTag: "geometry") << frontend;
128 }
129 {
130 auto frontend = new Qt3DRender::QShaderProgramBuilder;
131 frontend->setFragmentShaderGraph(QUrl::fromEncoded(url: "qrc:/fragment.json"));
132 QTest::newRow(dataTag: "fragment") << frontend;
133 }
134 {
135 auto frontend = new Qt3DRender::QShaderProgramBuilder;
136 frontend->setComputeShaderGraph(QUrl::fromEncoded(url: "qrc:/compute.json"));
137 QTest::newRow(dataTag: "compute") << frontend;
138 }
139 }
140
141 void shouldHavePropertiesMirroringFromItsPeer()
142 {
143 // GIVEN
144 QFETCH(Qt3DRender::QShaderProgramBuilder*, frontend);
145 Qt3DRender::Render::ShaderBuilder backend;
146 TestRenderer renderer;
147
148 // WHEN
149 backend.setRenderer(&renderer);
150 simulateInitializationSync(frontend, backend: &backend);
151
152 // THEN
153 QVERIFY(backend.isEnabled() == frontend->isEnabled());
154
155 if (frontend->shaderProgram())
156 QCOMPARE(backend.shaderProgramId(), frontend->shaderProgram()->id());
157 else
158 QVERIFY(backend.shaderProgramId().isNull());
159
160 QCOMPARE(backend.enabledLayers(), frontend->enabledLayers());
161
162 QCOMPARE(backend.shaderGraph(Qt3DRender::QShaderProgram::Vertex), frontend->vertexShaderGraph());
163 QCOMPARE(backend.shaderCode(Qt3DRender::QShaderProgram::Vertex), QByteArray());
164 QCOMPARE(backend.isShaderCodeDirty(Qt3DRender::QShaderProgram::Vertex), !frontend->vertexShaderGraph().isEmpty());
165
166 QCOMPARE(backend.shaderGraph(Qt3DRender::QShaderProgram::TessellationControl), frontend->tessellationControlShaderGraph());
167 QCOMPARE(backend.shaderCode(Qt3DRender::QShaderProgram::TessellationControl), QByteArray());
168 QCOMPARE(backend.isShaderCodeDirty(Qt3DRender::QShaderProgram::TessellationControl), !frontend->tessellationControlShaderGraph().isEmpty());
169
170 QCOMPARE(backend.shaderGraph(Qt3DRender::QShaderProgram::TessellationEvaluation), frontend->tessellationEvaluationShaderGraph());
171 QCOMPARE(backend.shaderCode(Qt3DRender::QShaderProgram::TessellationEvaluation), QByteArray());
172 QCOMPARE(backend.isShaderCodeDirty(Qt3DRender::QShaderProgram::TessellationEvaluation), !frontend->tessellationEvaluationShaderGraph().isEmpty());
173
174 QCOMPARE(backend.shaderGraph(Qt3DRender::QShaderProgram::Geometry), frontend->geometryShaderGraph());
175 QCOMPARE(backend.shaderCode(Qt3DRender::QShaderProgram::Geometry), QByteArray());
176 QCOMPARE(backend.isShaderCodeDirty(Qt3DRender::QShaderProgram::Geometry), !frontend->geometryShaderGraph().isEmpty());
177
178 QCOMPARE(backend.shaderGraph(Qt3DRender::QShaderProgram::Fragment), frontend->fragmentShaderGraph());
179 QCOMPARE(backend.shaderCode(Qt3DRender::QShaderProgram::Fragment), QByteArray());
180 QCOMPARE(backend.isShaderCodeDirty(Qt3DRender::QShaderProgram::Fragment), !frontend->fragmentShaderGraph().isEmpty());
181
182 QCOMPARE(backend.shaderGraph(Qt3DRender::QShaderProgram::Compute), frontend->computeShaderGraph());
183 QCOMPARE(backend.shaderCode(Qt3DRender::QShaderProgram::Compute), QByteArray());
184 QCOMPARE(backend.isShaderCodeDirty(Qt3DRender::QShaderProgram::Compute), !frontend->computeShaderGraph().isEmpty());
185
186 // WHEN
187 backend.cleanup();
188
189 // THEN
190 QVERIFY(!backend.isEnabled());
191 QVERIFY(backend.enabledLayers().isEmpty());
192 for (int i = 0; i <= Qt3DRender::QShaderProgram::Compute; i++) {
193 const auto type = static_cast<Qt3DRender::QShaderProgram::ShaderType>(i);
194 QCOMPARE(backend.shaderGraph(type), QUrl());
195 QCOMPARE(backend.shaderCode(type), QByteArray());
196 QVERIFY(!backend.isShaderCodeDirty(type));
197 }
198
199 delete frontend;
200 }
201
202 void shouldHandleEnablePropertyChange()
203 {
204 // GIVEN
205 Qt3DRender::Render::ShaderBuilder backend;
206 Qt3DRender::QShaderProgramBuilder frontend;
207 TestRenderer renderer;
208 backend.setRenderer(&renderer);
209 simulateInitializationSync(frontend: &frontend, backend: &backend);
210
211 // WHEN
212 frontend.setEnabled(false);
213 backend.syncFromFrontEnd(frontEnd: &frontend, firstTime: false);
214
215 // THEN
216 QVERIFY(!backend.isEnabled());
217 QCOMPARE(renderer.dirtyBits(), Qt3DRender::Render::AbstractRenderer::ShadersDirty);
218 renderer.resetDirty();
219
220 // WHEN
221 frontend.setEnabled(true);
222 backend.syncFromFrontEnd(frontEnd: &frontend, firstTime: false);
223 // AND
224 backend.cleanup();
225
226 // THEN
227 QVERIFY(!backend.isEnabled());
228 QCOMPARE(renderer.dirtyBits(), Qt3DRender::Render::AbstractRenderer::ShadersDirty);
229 renderer.resetDirty();
230 }
231
232 void shouldHandleShaderProgramPropertyChange()
233 {
234 // GIVEN
235 Qt3DRender::Render::ShaderBuilder backend;
236 Qt3DRender::QShaderProgramBuilder frontend;
237 TestRenderer renderer;
238 backend.setRenderer(&renderer);
239 simulateInitializationSync(frontend: &frontend, backend: &backend);
240
241 // WHEN
242 Qt3DRender::QShaderProgram prog;
243 frontend.setShaderProgram(&prog);
244 backend.syncFromFrontEnd(frontEnd: &frontend, firstTime: false);
245
246 // THEN
247 QCOMPARE(backend.shaderProgramId(), prog.id());
248 QCOMPARE(renderer.dirtyBits(), Qt3DRender::Render::AbstractRenderer::ShadersDirty);
249 renderer.resetDirty();
250
251 // WHEN
252 frontend.setShaderProgram(nullptr);
253 backend.syncFromFrontEnd(frontEnd: &frontend, firstTime: false);
254
255 // THEN
256 QVERIFY(backend.shaderProgramId().isNull());
257 QCOMPARE(renderer.dirtyBits(), Qt3DRender::Render::AbstractRenderer::ShadersDirty);
258 renderer.resetDirty();
259
260 // WHEN
261 frontend.setShaderProgram(&prog);
262 backend.syncFromFrontEnd(frontEnd: &frontend, firstTime: false);
263 // AND
264 backend.cleanup();
265
266 // THEN
267 QVERIFY(backend.shaderProgramId().isNull());
268 QCOMPARE(renderer.dirtyBits(), Qt3DRender::Render::AbstractRenderer::ShadersDirty);
269 renderer.resetDirty();
270 }
271
272 void shouldHandleEnabledLayersPropertyChange()
273 {
274 // GIVEN
275 qputenv(varName: "QT3D_DISABLE_SHADER_CACHE", value: "1");
276 Qt3DRender::Render::ShaderBuilder backend;
277 Qt3DRender::QShaderProgramBuilder frontend;
278 TestRenderer renderer;
279 backend.setRenderer(&renderer);
280 simulateInitializationSync(frontend: &frontend, backend: &backend);
281 const auto layers = QStringList() << "foo" << "bar";
282
283 static const std::pair<
284 Qt3DRender::QShaderProgram::ShaderType,
285 void (Qt3DRender::QShaderProgramBuilder::*)(const QUrl &)
286 >
287 shaderTypesToSetters[] = {
288 {Qt3DRender::QShaderProgram::Vertex, &Qt3DRender::QShaderProgramBuilder::setVertexShaderGraph},
289 {Qt3DRender::QShaderProgram::TessellationControl, &Qt3DRender::QShaderProgramBuilder::setTessellationControlShaderGraph},
290 {Qt3DRender::QShaderProgram::TessellationEvaluation, &Qt3DRender::QShaderProgramBuilder::setTessellationEvaluationShaderGraph},
291 {Qt3DRender::QShaderProgram::Geometry, &Qt3DRender::QShaderProgramBuilder::setGeometryShaderGraph},
292 {Qt3DRender::QShaderProgram::Fragment, &Qt3DRender::QShaderProgramBuilder::setFragmentShaderGraph},
293 {Qt3DRender::QShaderProgram::Compute, &Qt3DRender::QShaderProgramBuilder::setComputeShaderGraph},
294 };
295
296
297 for (auto it = std::begin(arr: shaderTypesToSetters), end = std::end(arr: shaderTypesToSetters); it != end; ++it) {
298 const auto graphUrl = QUrl::fromEncoded(url: "qrc:/input.json");
299 (frontend.*(it->second))(graphUrl);
300 }
301
302 // WHEN
303 frontend.setEnabledLayers(layers);
304 backend.syncFromFrontEnd(frontEnd: &frontend, firstTime: false);
305
306 // THEN
307 QCOMPARE(backend.enabledLayers(), layers);
308 for (int i = 0; i <= Qt3DRender::QShaderProgram::Compute; i++) {
309 const auto type = static_cast<Qt3DRender::QShaderProgram::ShaderType>(i);
310 QVERIFY(backend.isShaderCodeDirty(type));
311 backend.generateCode(type); // Resets the dirty flag
312 }
313 QCOMPARE(renderer.dirtyBits(), Qt3DRender::Render::AbstractRenderer::ShadersDirty);
314 renderer.resetDirty();
315
316 // WHEN
317 frontend.setEnabledLayers(layers);
318 backend.syncFromFrontEnd(frontEnd: &frontend, firstTime: false);
319
320 // THEN
321 QCOMPARE(backend.enabledLayers(), layers);
322 for (int i = 0; i <= Qt3DRender::QShaderProgram::Compute; i++) {
323 const auto type = static_cast<Qt3DRender::QShaderProgram::ShaderType>(i);
324 QVERIFY(!backend.isShaderCodeDirty(type));
325 backend.generateCode(type); // Resets the dirty flag
326 }
327 QCOMPARE(renderer.dirtyBits(), 0);
328 renderer.resetDirty();
329
330 // WHEN
331 frontend.setEnabledLayers(QStringList());
332 backend.syncFromFrontEnd(frontEnd: &frontend, firstTime: false);
333
334 // THEN
335 QVERIFY(backend.shaderProgramId().isNull());
336 for (int i = 0; i <= Qt3DRender::QShaderProgram::Compute; i++) {
337 const auto type = static_cast<Qt3DRender::QShaderProgram::ShaderType>(i);
338 QVERIFY(backend.isShaderCodeDirty(type));
339 backend.generateCode(type); // Resets the dirty flag
340 }
341 QCOMPARE(renderer.dirtyBits(), Qt3DRender::Render::AbstractRenderer::ShadersDirty);
342 renderer.resetDirty();
343
344 // WHEN
345 frontend.setEnabledLayers(layers);
346 backend.syncFromFrontEnd(frontEnd: &frontend, firstTime: false);
347 // AND
348 backend.cleanup();
349
350 // THEN
351 QVERIFY(backend.enabledLayers().isEmpty());
352 for (int i = 0; i <= Qt3DRender::QShaderProgram::Compute; i++) {
353 const auto type = static_cast<Qt3DRender::QShaderProgram::ShaderType>(i);
354 QVERIFY(!backend.isShaderCodeDirty(type));
355 backend.generateCode(type); // Resets the dirty flag
356 }
357 QCOMPARE(renderer.dirtyBits(), Qt3DRender::Render::AbstractRenderer::ShadersDirty);
358 renderer.resetDirty();
359 }
360
361 void shouldHandleShaderGraphPropertiesChanges_data()
362 {
363 QTest::addColumn<Qt3DRender::QShaderProgram::ShaderType>(name: "type");
364 QTest::addColumn<QUrl>(name: "graphUrl");
365
366 QTest::newRow(dataTag: "vertex") << Qt3DRender::QShaderProgram::Vertex
367 << QUrl::fromEncoded(url: "qrc:/vertex.json");
368
369 QTest::newRow(dataTag: "tessControl") << Qt3DRender::QShaderProgram::TessellationControl
370 << QUrl::fromEncoded(url: "qrc:/tesscontrol.json");
371
372 QTest::newRow(dataTag: "tessEval") << Qt3DRender::QShaderProgram::TessellationEvaluation
373 << QUrl::fromEncoded(url: "qrc:/tesseval.json");
374
375 QTest::newRow(dataTag: "geometry") << Qt3DRender::QShaderProgram::Geometry
376 << QUrl::fromEncoded(url: "qrc:/geometry.json");
377
378 QTest::newRow(dataTag: "fragment") << Qt3DRender::QShaderProgram::Fragment
379 << QUrl::fromEncoded(url: "qrc:/fragment.json");
380
381 QTest::newRow(dataTag: "compute") << Qt3DRender::QShaderProgram::Compute
382 << QUrl::fromEncoded(url: "qrc:/compute.json");
383 }
384
385 void shouldHandleShaderGraphPropertiesChanges()
386 {
387 // GIVEN
388 QFETCH(Qt3DRender::QShaderProgram::ShaderType, type);
389 QFETCH(QUrl, graphUrl);
390
391 Qt3DRender::Render::ShaderBuilder backend;
392 Qt3DRender::QShaderProgramBuilder frontend;
393 TestRenderer renderer;
394 backend.setRenderer(&renderer);
395 simulateInitializationSync(frontend: &frontend, backend: &backend);
396
397 static const QHash<
398 Qt3DRender::QShaderProgram::ShaderType,
399 void (Qt3DRender::QShaderProgramBuilder::*)(const QUrl &)
400 >
401 shaderTypesToSetters = {
402 {Qt3DRender::QShaderProgram::Vertex, &Qt3DRender::QShaderProgramBuilder::setVertexShaderGraph},
403 {Qt3DRender::QShaderProgram::TessellationControl, &Qt3DRender::QShaderProgramBuilder::setTessellationControlShaderGraph},
404 {Qt3DRender::QShaderProgram::TessellationEvaluation, &Qt3DRender::QShaderProgramBuilder::setTessellationEvaluationShaderGraph},
405 {Qt3DRender::QShaderProgram::Geometry, &Qt3DRender::QShaderProgramBuilder::setGeometryShaderGraph},
406 {Qt3DRender::QShaderProgram::Fragment, &Qt3DRender::QShaderProgramBuilder::setFragmentShaderGraph},
407 {Qt3DRender::QShaderProgram::Compute, &Qt3DRender::QShaderProgramBuilder::setComputeShaderGraph},
408 };
409
410 // WHEN
411
412 (frontend.*(shaderTypesToSetters[type]))(QUrl());
413 backend.syncFromFrontEnd(frontEnd: &frontend, firstTime: false);
414
415 // THEN
416 QCOMPARE(backend.shaderGraph(type), QUrl());
417 QVERIFY(!backend.isShaderCodeDirty(type));
418 QVERIFY(backend.shaderCode(type).isEmpty());
419 QCOMPARE(renderer.dirtyBits(), 0);
420 renderer.resetDirty();
421
422 // WHEN
423 (frontend.*(shaderTypesToSetters[type]))(graphUrl);
424 backend.syncFromFrontEnd(frontEnd: &frontend, firstTime: false);
425
426 // THEN
427 QCOMPARE(backend.shaderGraph(type), graphUrl);
428 QVERIFY(backend.isShaderCodeDirty(type));
429 QVERIFY(backend.shaderCode(type).isEmpty());
430 QCOMPARE(renderer.dirtyBits(), Qt3DRender::Render::AbstractRenderer::ShadersDirty);
431 renderer.resetDirty();
432
433 // WHEN
434 (frontend.*(shaderTypesToSetters[type]))(QUrl());
435 backend.syncFromFrontEnd(frontEnd: &frontend, firstTime: false);
436
437 // THEN
438 QCOMPARE(backend.shaderGraph(type), QUrl());
439 QVERIFY(backend.isShaderCodeDirty(type));
440 QVERIFY(backend.shaderCode(type).isEmpty());
441 QCOMPARE(renderer.dirtyBits(), Qt3DRender::Render::AbstractRenderer::ShadersDirty);
442 renderer.resetDirty();
443
444 // WHEN
445 (frontend.*(shaderTypesToSetters[type]))(graphUrl);
446 backend.syncFromFrontEnd(frontEnd: &frontend, firstTime: false);
447 // AND
448 backend.cleanup();
449
450 // THEN
451 QCOMPARE(backend.shaderGraph(type), QUrl());
452 QVERIFY(!backend.isShaderCodeDirty(type));
453 QVERIFY(backend.shaderCode(type).isEmpty());
454 QCOMPARE(renderer.dirtyBits(), Qt3DRender::Render::AbstractRenderer::ShadersDirty);
455 renderer.resetDirty();
456 }
457
458 void shouldHandleShaderCodeGeneration_data()
459 {
460 QTest::addColumn<Qt3DRender::QShaderProgram::ShaderType>(name: "type");
461
462 QTest::newRow(dataTag: "vertex") << Qt3DRender::QShaderProgram::Vertex;
463 QTest::newRow(dataTag: "tessControl") << Qt3DRender::QShaderProgram::TessellationControl;
464 QTest::newRow(dataTag: "tessEval") << Qt3DRender::QShaderProgram::TessellationEvaluation;
465 QTest::newRow(dataTag: "geometry") << Qt3DRender::QShaderProgram::Geometry;
466 QTest::newRow(dataTag: "fragment") << Qt3DRender::QShaderProgram::Fragment;
467 QTest::newRow(dataTag: "compute") << Qt3DRender::QShaderProgram::Compute;
468 }
469
470 void shouldHandleShaderCodeGeneration()
471 {
472 // GIVEN
473 qputenv(varName: "QT3D_DISABLE_SHADER_CACHE", value: "1");
474 Qt3DRender::Render::ShaderBuilder::setPrototypesFile(":/prototypes.json");
475 QVERIFY(!Qt3DRender::Render::ShaderBuilder::getPrototypeNames().isEmpty());
476
477 QFETCH(Qt3DRender::QShaderProgram::ShaderType, type);
478
479 const auto gl3Api = []{
480 auto api = Qt3DRender::GraphicsApiFilterData();
481 api.m_api = Qt3DRender::QGraphicsApiFilter::OpenGL;
482 api.m_profile = Qt3DRender::QGraphicsApiFilter::CoreProfile;
483 api.m_major = 3;
484 api.m_minor = 2;
485 return api;
486 }();
487
488 const auto es2Api = []{
489 auto api = Qt3DRender::GraphicsApiFilterData();
490 api.m_api = Qt3DRender::QGraphicsApiFilter::OpenGLES;
491 api.m_major = 2;
492 api.m_minor = 0;
493 return api;
494 }();
495
496 const auto readCode = [](const QString &suffix) -> QByteArray {
497 const auto filePath = QStringLiteral(":/output.") + suffix;
498 QFile file(filePath);
499 if (!file.open(flags: QFile::ReadOnly | QFile::Text))
500 qFatal(msg: "File open failed: %s", qPrintable(filePath));
501 return file.readAll();
502 };
503
504 const auto gl3Code = readCode("gl3");
505 const auto es2Code = readCode("es2");
506
507 Qt3DRender::Render::ShaderBuilder backend;
508
509 // WHEN
510 const auto graphUrl = QUrl::fromEncoded(url: "qrc:/input.json");
511 backend.setShaderGraph(type, url: graphUrl);
512
513 // THEN
514 QCOMPARE(backend.shaderGraph(type), graphUrl);
515 QVERIFY(backend.isShaderCodeDirty(type));
516 QVERIFY(backend.shaderCode(type).isEmpty());
517
518 // WHEN
519 backend.setGraphicsApi(gl3Api);
520 backend.generateCode(type);
521
522 // THEN
523 QCOMPARE(backend.shaderGraph(type), graphUrl);
524 QVERIFY(!backend.isShaderCodeDirty(type));
525 QCOMPARE(backend.shaderCode(type), gl3Code);
526
527 // WHEN
528 backend.setGraphicsApi(es2Api);
529
530 // THEN
531 QCOMPARE(backend.shaderGraph(type), graphUrl);
532 QVERIFY(backend.isShaderCodeDirty(type));
533 QCOMPARE(backend.shaderCode(type), gl3Code);
534
535 // WHEN
536 backend.generateCode(type);
537
538 // THEN
539 QCOMPARE(backend.shaderGraph(type), graphUrl);
540 QVERIFY(!backend.isShaderCodeDirty(type));
541 QCOMPARE(backend.shaderCode(type), es2Code);
542 }
543
544 void checkCodeUpdatedNotification_data()
545 {
546 QTest::addColumn<Qt3DRender::QShaderProgram::ShaderType>(name: "type");
547 QTest::addColumn<Qt3DRender::QShaderProgram::ShaderType>(name: "notificationType");
548
549 QTest::newRow(dataTag: "vertex") << Qt3DRender::QShaderProgram::Vertex << Qt3DRender::QShaderProgram::Vertex;
550 QTest::newRow(dataTag: "tessControl") << Qt3DRender::QShaderProgram::TessellationControl << Qt3DRender::QShaderProgram::TessellationControl;
551 QTest::newRow(dataTag: "tessEval") << Qt3DRender::QShaderProgram::TessellationEvaluation << Qt3DRender::QShaderProgram::TessellationEvaluation;
552 QTest::newRow(dataTag: "geometry") << Qt3DRender::QShaderProgram::Geometry << Qt3DRender::QShaderProgram::Geometry;
553 QTest::newRow(dataTag: "fragment") << Qt3DRender::QShaderProgram::Fragment << Qt3DRender::QShaderProgram::Fragment;
554 QTest::newRow(dataTag: "compute") << Qt3DRender::QShaderProgram::Compute << Qt3DRender::QShaderProgram::Compute;
555 }
556
557
558 void checkCodeUpdatedNotification()
559 {
560 // GIVEN
561 qputenv(varName: "QT3D_DISABLE_SHADER_CACHE", value: "1");
562 Qt3DRender::Render::ShaderBuilder::setPrototypesFile(":/prototypes.json");
563 QVERIFY(!Qt3DRender::Render::ShaderBuilder::getPrototypeNames().isEmpty());
564 QFETCH(Qt3DRender::QShaderProgram::ShaderType, type);
565 QFETCH(Qt3DRender::QShaderProgram::ShaderType, notificationType);
566 Q_UNUSED(notificationType)
567
568 const auto gl3Api = []{
569 auto api = Qt3DRender::GraphicsApiFilterData();
570 api.m_api = Qt3DRender::QGraphicsApiFilter::OpenGL;
571 api.m_profile = Qt3DRender::QGraphicsApiFilter::CoreProfile;
572 api.m_major = 3;
573 api.m_minor = 2;
574 return api;
575 }();
576
577 const auto readCode = [](const QString &suffix) -> QByteArray {
578 const auto filePath = QStringLiteral(":/output.") + suffix;
579 QFile file(filePath);
580 if (!file.open(flags: QFile::ReadOnly | QFile::Text))
581 qFatal(msg: "File open failed: %s", qPrintable(filePath));
582 return file.readAll();
583 };
584
585 const auto gl3Code = readCode("gl3");
586
587 Qt3DRender::Render::ShaderBuilder backend;
588 TestArbiter arbiter;
589 Qt3DCore::QBackendNodePrivate::get(n: &backend)->setArbiter(&arbiter);
590
591
592 // WHEN
593 const auto graphUrl = QUrl::fromEncoded(url: "qrc:/input.json");
594 backend.setShaderGraph(type, url: graphUrl);
595
596 // THEN
597 QCOMPARE(backend.shaderGraph(type), graphUrl);
598 QVERIFY(backend.isShaderCodeDirty(type));
599 QVERIFY(backend.shaderCode(type).isEmpty());
600
601 // WHEN
602 backend.setGraphicsApi(gl3Api);
603 backend.generateCode(type);
604
605 // THEN
606 QCOMPARE(backend.shaderGraph(type), graphUrl);
607 QVERIFY(!backend.isShaderCodeDirty(type));
608 QCOMPARE(backend.shaderCode(type), gl3Code);
609 }
610
611 void checkFileCaching()
612 {
613 // GIVEN
614 qunsetenv(varName: "QT3D_DISABLE_SHADER_CACHE");
615 QTemporaryDir cacheDir;
616
617 if (!cacheDir.isValid()) {
618 QSKIP("Unable to generate cache dir, skipping");
619 return;
620 }
621 const auto gl3Api = []{
622 auto api = Qt3DRender::GraphicsApiFilterData();
623 api.m_api = Qt3DRender::QGraphicsApiFilter::OpenGL;
624 api.m_profile = Qt3DRender::QGraphicsApiFilter::CoreProfile;
625 api.m_major = 3;
626 api.m_minor = 2;
627 return api;
628 }();
629 const auto gl2Api = []{
630 auto api = Qt3DRender::GraphicsApiFilterData();
631 api.m_api = Qt3DRender::QGraphicsApiFilter::OpenGL;
632 api.m_profile = Qt3DRender::QGraphicsApiFilter::NoProfile;
633 api.m_major = 2;
634 api.m_minor = 0;
635 return api;
636 }();
637 Qt3DRender::Render::ShaderBuilder::setPrototypesFile(":/prototypes.json");
638 qputenv(varName: "QT3D_WRITABLE_CACHE_PATH", value: cacheDir.path().toUtf8());
639
640 // THEN
641 QVERIFY(QDir(cacheDir.path()).entryList(QDir::Files).empty());
642 QByteArray hashKey;
643 {
644 // WHEN
645 Qt3DRender::Render::ShaderBuilder b;
646 b.setGraphicsApi(gl3Api);
647 const auto graphUrl = QUrl::fromEncoded(url: "qrc:/input.json");
648 b.setShaderGraph(type: Qt3DRender::QShaderProgram::Vertex, url: graphUrl);
649 b.generateCode(type: Qt3DRender::QShaderProgram::Vertex);
650
651 // THEN
652 QCOMPARE(QDir(cacheDir.path()).entryList(QDir::Files).count(), 1);
653
654 hashKey = b.hashKeyForShaderGraph(type: Qt3DRender::QShaderProgram::Vertex);
655 QCOMPARE(hashKey.length(), 40);
656
657 QCOMPARE(QDir(cacheDir.path()).entryList(QDir::Files).first(),
658 QString::fromUtf8(hashKey) + QLatin1String(".qt3d"));
659 }
660 {
661 // WHEN
662 Qt3DRender::Render::ShaderBuilder b;
663 b.setGraphicsApi(gl3Api);
664 const auto graphUrl = QUrl::fromEncoded(url: "qrc:/input.json");
665 b.setShaderGraph(type: Qt3DRender::QShaderProgram::Vertex, url: graphUrl);
666 b.generateCode(type: Qt3DRender::QShaderProgram::Vertex);
667
668 // THEN
669 QCOMPARE(QDir(cacheDir.path()).entryList(QDir::Files).count(), 1);
670 QCOMPARE(QDir(cacheDir.path()).entryList(QDir::Files).first(),
671 QString::fromUtf8(hashKey) + QLatin1String(".qt3d"));
672 }
673 {
674 // WHEN
675 Qt3DRender::Render::ShaderBuilder b;
676 b.setGraphicsApi(gl2Api);
677 const auto graphUrl = QUrl::fromEncoded(url: "qrc:/input.json");
678 b.setShaderGraph(type: Qt3DRender::QShaderProgram::Vertex, url: graphUrl);
679 b.generateCode(type: Qt3DRender::QShaderProgram::Vertex);
680 QByteArray gl2HashKey = b.hashKeyForShaderGraph(type: Qt3DRender::QShaderProgram::Vertex);
681
682 // THEN
683 QCOMPARE(QDir(cacheDir.path()).entryList(QDir::Files).count(), 2);
684 QVERIFY(gl2HashKey != hashKey);
685 }
686 }
687
688 void checkRuntimeCaching()
689 {
690 // GIVEN
691 qunsetenv(varName: "QT3D_DISABLE_SHADER_CACHE");
692 TestRenderer renderer;
693 QTemporaryDir cacheDir;
694
695 if (!cacheDir.isValid()) {
696 QSKIP("Unable to generate cache dir, skipping");
697 return;
698 }
699 const auto gl3Api = []{
700 auto api = Qt3DRender::GraphicsApiFilterData();
701 api.m_api = Qt3DRender::QGraphicsApiFilter::OpenGL;
702 api.m_profile = Qt3DRender::QGraphicsApiFilter::CoreProfile;
703 api.m_major = 3;
704 api.m_minor = 2;
705 return api;
706 }();
707 Qt3DRender::Render::ShaderBuilder::setPrototypesFile(":/prototypes.json");
708 qputenv(varName: "QT3D_WRITABLE_CACHE_PATH", value: cacheDir.path().toUtf8());
709
710 // THEN
711 QVERIFY(QDir(cacheDir.path()).entryList(QDir::Files).empty());
712
713 // WHEN
714 Qt3DRender::Render::ShaderBuilder b;
715 b.setGraphicsApi(gl3Api);
716 const auto graphUrl = QUrl::fromEncoded(url: "qrc:/input.json");
717 b.setShaderGraph(type: Qt3DRender::QShaderProgram::Vertex, url: graphUrl);
718 b.generateCode(type: Qt3DRender::QShaderProgram::Vertex);
719
720 // THEN
721 QCOMPARE(QDir(cacheDir.path()).entryList(QDir::Files).count(), 1);
722
723 const QByteArray hashKey = b.hashKeyForShaderGraph(type: Qt3DRender::QShaderProgram::Vertex);
724 QCOMPARE(hashKey.length(), 40);
725
726 QCOMPARE(QDir(cacheDir.path()).entryList(QDir::Files).first(),
727 QString::fromUtf8(hashKey) + QLatin1String(".qt3d"));
728
729 QVERIFY(!renderer.containsGeneratedShaderGraph(hashKey));
730
731 // WHEN
732 b.setRenderer(&renderer);
733 b.generateCode(type: Qt3DRender::QShaderProgram::Vertex);
734
735 // THEN
736 QVERIFY(renderer.containsGeneratedShaderGraph(hashKey));
737 }
738
739 void checkDontUseCache()
740 {
741 // GIVEN
742 QTemporaryDir cacheDir;
743
744 if (!cacheDir.isValid()) {
745 QSKIP("Unable to generate cache dir, skipping");
746 return;
747 }
748 const auto gl3Api = []{
749 auto api = Qt3DRender::GraphicsApiFilterData();
750 api.m_api = Qt3DRender::QGraphicsApiFilter::OpenGL;
751 api.m_profile = Qt3DRender::QGraphicsApiFilter::CoreProfile;
752 api.m_major = 3;
753 api.m_minor = 2;
754 return api;
755 }();
756 Qt3DRender::Render::ShaderBuilder::setPrototypesFile(":/prototypes.json");
757
758 // THEN
759 QVERIFY(QDir(cacheDir.path()).entryList(QDir::Files).empty());
760
761 // WHEN
762 qputenv(varName: "QT3D_DISABLE_SHADER_CACHE", value: "1");
763 Qt3DRender::Render::ShaderBuilder b;
764 b.setGraphicsApi(gl3Api);
765 const auto graphUrl = QUrl::fromEncoded(url: "qrc:/input.json");
766 b.setShaderGraph(type: Qt3DRender::QShaderProgram::Vertex, url: graphUrl);
767 b.generateCode(type: Qt3DRender::QShaderProgram::Vertex);
768
769 // THEN
770 QCOMPARE(QDir(cacheDir.path()).entryList(QDir::Files).count(), 0);
771 }
772
773 void checkForceRebuildCache()
774 {
775 // GIVEN
776 QTemporaryDir cacheDir;
777
778 if (!cacheDir.isValid()) {
779 QSKIP("Unable to generate cache dir, skipping");
780 return;
781 }
782 const auto gl3Api = []{
783 auto api = Qt3DRender::GraphicsApiFilterData();
784 api.m_api = Qt3DRender::QGraphicsApiFilter::OpenGL;
785 api.m_profile = Qt3DRender::QGraphicsApiFilter::CoreProfile;
786 api.m_major = 3;
787 api.m_minor = 2;
788 return api;
789 }();
790 Qt3DRender::Render::ShaderBuilder::setPrototypesFile(":/prototypes.json");
791
792 // THEN
793 QVERIFY(QDir(cacheDir.path()).entryList(QDir::Files).empty());
794
795 // WHEN
796 qputenv(varName: "QT3D_WRITABLE_CACHE_PATH", value: cacheDir.path().toUtf8());
797 qputenv(varName: "QT3D_DISABLE_SHADER_CACHE", value: "1");
798 qputenv(varName: "QT3D_REBUILD_SHADER_CACHE", value: "1");
799 Qt3DRender::Render::ShaderBuilder b;
800 b.setGraphicsApi(gl3Api);
801 const auto graphUrl = QUrl::fromEncoded(url: "qrc:/input.json");
802 b.setShaderGraph(type: Qt3DRender::QShaderProgram::Vertex, url: graphUrl);
803 b.generateCode(type: Qt3DRender::QShaderProgram::Vertex);
804
805 // THEN -> We have rebuilt the shader file (even if we don't use it)
806 QCOMPARE(QDir(cacheDir.path()).entryList(QDir::Files).count(), 1);
807 }
808};
809
810QTEST_MAIN(tst_ShaderBuilder)
811
812#include "tst_shaderbuilder.moc"
813

source code of qt3d/tests/auto/render/shaderbuilder/tst_shaderbuilder.cpp