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 <qbackendnodetester.h>
31#include <Qt3DRender/private/shader_p.h>
32#include <Qt3DRender/private/managers_p.h>
33#include <Qt3DRender/qshaderprogram.h>
34#include "testrenderer.h"
35
36class tst_RenderShader : public Qt3DCore::QBackendNodeTester
37{
38 Q_OBJECT
39private slots:
40
41 void hasCoherentInitialState();
42 void matchesFrontendPeer();
43 void cleanupLeavesACoherentState();
44 void dealWithPropertyChanges_data();
45 void dealWithPropertyChanges();
46 void dealWithFormatChanges();
47 void checkSetRendererDirtyOnInitialization();
48 void allowToChangeShaderCode_data();
49 void allowToChangeShaderCode();
50 void checkShaderManager();
51};
52
53
54Qt3DRender::QShaderProgram *createFrontendShader(Qt3DRender::QShaderProgram::Format format = Qt3DRender::QShaderProgram::GLSL)
55{
56 Qt3DRender::QShaderProgram *shader = new Qt3DRender::QShaderProgram();
57 shader->setFormat(format);
58
59 shader->setVertexShaderCode(QByteArrayLiteral(
60 "#version 150"\
61 "in vec3 vertexPosition;"\
62 "in vec3 vertexColor; "\
63 "out vec3 color;"\
64 "void main()"\
65 "{"\
66 " color = vertexColor;"\
67 " gl_Position = vec4( vertexPosition, 1.0 );"\
68 "}"));
69
70 shader->setFragmentShaderCode(QByteArrayLiteral(
71 "#version 150"\
72 "in vec3 color;"\
73 "out vec4 fragColor;"\
74 "void main()"\
75 "{"\
76 " fragColor = vec4( color, 1.0 );"\
77 "}"));
78
79 return shader;
80}
81
82void tst_RenderShader::hasCoherentInitialState()
83{
84 Qt3DRender::Render::Shader *shader = new Qt3DRender::Render::Shader();
85
86 QCOMPARE(shader->status(), Qt3DRender::QShaderProgram::NotReady);
87 QCOMPARE(shader->format(), Qt3DRender::QShaderProgram::GLSL);
88 QVERIFY(shader->log().isEmpty());
89 QCOMPARE(shader->isDirty(), false);
90}
91
92void tst_RenderShader::matchesFrontendPeer()
93{
94 QScopedPointer<Qt3DRender::QShaderProgram> frontend(createFrontendShader());
95 TestRenderer renderer;
96 Qt3DRender::Render::Shader backend;
97
98 backend.setRenderer(&renderer);
99 simulateInitializationSync(frontend: frontend.data(), backend: &backend);
100 QCOMPARE(backend.isDirty(), true);
101
102 for (int i = Qt3DRender::QShaderProgram::Vertex; i <= Qt3DRender::QShaderProgram::Compute; ++i)
103 QCOMPARE(backend.shaderCode()[i],
104 frontend->shaderCode(static_cast<Qt3DRender::QShaderProgram::ShaderType>(i)));
105 QCOMPARE(backend.format(), frontend->format());
106}
107
108void tst_RenderShader::cleanupLeavesACoherentState()
109{
110 QScopedPointer<Qt3DRender::QShaderProgram> frontend(createFrontendShader(format: Qt3DRender::QShaderProgram::SPIRV));
111 TestRenderer renderer;
112 Qt3DRender::Render::Shader shader;
113
114 shader.setRenderer(&renderer);
115 simulateInitializationSync(frontend: frontend.data(), backend: &shader);
116
117 shader.cleanup();
118
119 QCOMPARE(shader.isDirty(), false);
120 QCOMPARE(shader.status(), Qt3DRender::QShaderProgram::NotReady);
121 QCOMPARE(shader.format(), Qt3DRender::QShaderProgram::GLSL);
122}
123
124void tst_RenderShader::dealWithPropertyChanges_data()
125{
126 QTest::addColumn<Qt3DRender::QShaderProgram::ShaderType>(name: "type");
127
128 QTest::newRow(dataTag: "vertex") << Qt3DRender::QShaderProgram::Vertex;
129
130 QTest::newRow(dataTag: "tessControl") << Qt3DRender::QShaderProgram::TessellationControl;
131
132 QTest::newRow(dataTag: "tessEval") << Qt3DRender::QShaderProgram::TessellationEvaluation;
133
134 QTest::newRow(dataTag: "geometry") << Qt3DRender::QShaderProgram::Geometry;
135
136 QTest::newRow(dataTag: "fragment") << Qt3DRender::QShaderProgram::Fragment;
137
138 QTest::newRow(dataTag: "compute") << Qt3DRender::QShaderProgram::Compute;
139}
140
141void tst_RenderShader::dealWithPropertyChanges()
142{
143 // GIVEN
144 QFETCH(Qt3DRender::QShaderProgram::ShaderType, type);
145
146 Qt3DRender::Render::Shader backend;
147 Qt3DRender::QShaderProgram shader;
148
149 TestRenderer renderer;
150 backend.setRenderer(&renderer);
151 simulateInitializationSync(frontend: &shader, backend: &backend);
152
153 // WHEN
154 shader.setShaderCode(type, QByteArrayLiteral("foo"));
155 backend.syncFromFrontEnd(frontEnd: &shader, firstTime: false);
156
157 // THEN
158 QCOMPARE(backend.shaderCode().at(type), QByteArrayLiteral("foo"));
159 QCOMPARE(renderer.dirtyBits(), Qt3DRender::Render::AbstractRenderer::ShadersDirty);
160 QCOMPARE(backend.isDirty(), true);
161
162 renderer.resetDirty();
163 QCOMPARE(renderer.dirtyBits(), 0);
164 backend.unsetDirty();
165
166 // WHEN
167 shader.setShaderCode(type, QByteArrayLiteral("foo"));
168 backend.syncFromFrontEnd(frontEnd: &shader, firstTime: false);
169
170 // THEN
171 QCOMPARE(backend.shaderCode().at(type), QByteArrayLiteral("foo"));
172 QCOMPARE(renderer.dirtyBits(), 0);
173 QCOMPARE(backend.isDirty(), false);
174
175 // WHEN
176 shader.setShaderCode(type, QByteArrayLiteral("bar"));
177 backend.syncFromFrontEnd(frontEnd: &shader, firstTime: false);
178
179 // THEN
180 QCOMPARE(backend.shaderCode().at(type), QByteArrayLiteral("bar"));
181 QCOMPARE(renderer.dirtyBits(), Qt3DRender::Render::AbstractRenderer::ShadersDirty);
182 renderer.resetDirty();
183 QCOMPARE(backend.isDirty(), true);
184}
185
186void tst_RenderShader::dealWithFormatChanges()
187{
188 // GIVEN
189 Qt3DRender::Render::Shader backend;
190 Qt3DRender::QShaderProgram shader;
191 TestRenderer renderer;
192 backend.setRenderer(&renderer);
193 simulateInitializationSync(frontend: &shader, backend: &backend);
194
195 // WHEN
196 shader.setFormat(Qt3DRender::QShaderProgram::GLSL);
197 backend.syncFromFrontEnd(frontEnd: &shader, firstTime: false);
198
199 // THEN
200 QCOMPARE(backend.format(), Qt3DRender::QShaderProgram::GLSL);
201 QCOMPARE(backend.isDirty(), false);
202 QCOMPARE(renderer.dirtyBits(), 0);
203
204 // WHEN
205 shader.setFormat(Qt3DRender::QShaderProgram::SPIRV);
206 backend.syncFromFrontEnd(frontEnd: &shader, firstTime: false);
207
208 // THEN
209 QCOMPARE(backend.format(), Qt3DRender::QShaderProgram::SPIRV);
210 QCOMPARE(renderer.dirtyBits(), Qt3DRender::Render::AbstractRenderer::ShadersDirty);
211 QCOMPARE(backend.isDirty(), true);
212
213 renderer.resetDirty();
214 backend.unsetDirty();
215
216 // WHEN
217 shader.setFormat(Qt3DRender::QShaderProgram::SPIRV);
218 backend.syncFromFrontEnd(frontEnd: &shader, firstTime: false);
219
220 // THEN
221 QCOMPARE(backend.isDirty(), false);
222 QCOMPARE(renderer.dirtyBits(), 0);
223}
224
225void tst_RenderShader::checkSetRendererDirtyOnInitialization()
226{
227 // GIVEN
228 QScopedPointer<Qt3DRender::QShaderProgram> frontend(createFrontendShader());
229 Qt3DRender::Render::Shader shader;
230 TestRenderer renderer;
231
232 shader.setRenderer(&renderer);
233
234 // THEN
235 QCOMPARE(renderer.dirtyBits(), 0);
236
237 // WHEN
238 simulateInitializationSync(frontend: frontend.data(), backend: &shader);
239
240 // THEN
241 QCOMPARE(renderer.dirtyBits(), Qt3DRender::Render::AbstractRenderer::ShadersDirty);
242}
243
244void tst_RenderShader::allowToChangeShaderCode_data()
245{
246 dealWithPropertyChanges_data();
247}
248
249void tst_RenderShader::allowToChangeShaderCode()
250{
251 // GIVEN
252 QFETCH(Qt3DRender::QShaderProgram::ShaderType, type);
253
254 Qt3DRender::Render::Shader backend;
255 TestRenderer renderer;
256 backend.setRenderer(&renderer);
257
258 // WHEN
259 backend.setShaderCode(type, QByteArrayLiteral("foo"));
260
261 // THEN
262 QCOMPARE(backend.shaderCode().at(type), QByteArrayLiteral("foo"));
263 QCOMPARE(renderer.dirtyBits(), Qt3DRender::Render::AbstractRenderer::ShadersDirty);
264 renderer.resetDirty();
265
266 // WHEN
267 backend.setShaderCode(type, QByteArrayLiteral("foo"));
268
269 // THEN
270 QCOMPARE(backend.shaderCode().at(type), QByteArrayLiteral("foo"));
271 QCOMPARE(renderer.dirtyBits(), 0);
272 renderer.resetDirty();
273
274 // WHEN
275 backend.setShaderCode(type, QByteArrayLiteral("bar"));
276
277 // THEN
278 QCOMPARE(backend.shaderCode().at(type), QByteArrayLiteral("bar"));
279 QCOMPARE(renderer.dirtyBits(), Qt3DRender::Render::AbstractRenderer::ShadersDirty);
280 renderer.resetDirty();
281}
282
283void tst_RenderShader::checkShaderManager()
284{
285 // GIVEN
286 Qt3DRender::QShaderProgram shader;
287 TestRenderer renderer;
288 Qt3DRender::Render::ShaderManager manager;
289 Qt3DRender::Render::ShaderFunctor creationFunctor(&renderer, &manager);
290
291 // THEN
292 QVERIFY(manager.shaderIdsToCleanup().isEmpty());
293
294 // WHEN
295 Qt3DCore::QNodeCreatedChangeBase changeObj(&shader);
296 Qt3DCore::QNodeCreatedChangeBasePtr changePtr(&changeObj, [](Qt3DCore::QNodeCreatedChangeBase *) {});
297 auto backend = creationFunctor.create(change: changePtr);
298
299 // THEN
300 QVERIFY(backend != nullptr);
301 QVERIFY(manager.shaderIdsToCleanup().isEmpty());
302
303 {
304 // WHEN
305 auto sameBackend = creationFunctor.get(id: shader.id());
306 // THEN
307 QCOMPARE(backend, sameBackend);
308 }
309
310 // WHEN
311 creationFunctor.destroy(id: shader.id());
312
313 // THEN -> Should be in list of ids to remove and return null on get
314 QVERIFY(manager.hasShaderIdToCleanup(shader.id()));
315 QVERIFY(creationFunctor.get(shader.id()) == nullptr);
316
317 // WHEN -> Should be removed from list of ids to remove
318 creationFunctor.create(change: changePtr);
319
320 // THEN
321 QVERIFY(manager.shaderIdsToCleanup().isEmpty());
322 QCOMPARE(creationFunctor.get(shader.id()), backend);
323}
324
325QTEST_APPLESS_MAIN(tst_RenderShader)
326
327#include "tst_shader.moc"
328

source code of qt3d/tests/auto/render/shader/tst_shader.cpp