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 | |
41 | Q_DECLARE_METATYPE(Qt3DRender::QShaderProgram::ShaderType) |
42 | |
43 | class tst_ShaderBuilder : public Qt3DCore::QBackendNodeTester |
44 | { |
45 | Q_OBJECT |
46 | private 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 | |
810 | QTEST_MAIN(tst_ShaderBuilder) |
811 | |
812 | #include "tst_shaderbuilder.moc" |
813 | |