1/****************************************************************************
2**
3** Copyright (C) 2019 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the test suite 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/QtTest>
30#include <QFile>
31#include <QtGui/private/qshaderdescription_p_p.h>
32#include <QtGui/private/qshader_p_p.h>
33
34class tst_QShader : public QObject
35{
36 Q_OBJECT
37
38private slots:
39 void simpleCompileCheckResults();
40 void genVariants();
41 void shaderDescImplicitSharing();
42 void bakedShaderImplicitSharing();
43 void mslResourceMapping();
44 void loadV3();
45 void serializeShaderDesc();
46 void comparison();
47 void loadV4();
48};
49
50static QShader getShader(const QString &name)
51{
52 QFile f(name);
53 if (f.open(flags: QIODevice::ReadOnly))
54 return QShader::fromSerialized(data: f.readAll());
55
56 return QShader();
57}
58
59void tst_QShader::simpleCompileCheckResults()
60{
61 QShader s = getShader(name: QLatin1String(":/data/color_spirv_v1.vert.qsb"));
62 QVERIFY(s.isValid());
63 QCOMPARE(QShaderPrivate::get(&s)->qsbVersion, 1);
64 QCOMPARE(s.availableShaders().count(), 1);
65
66 const QShaderCode shader = s.shader(key: QShaderKey(QShader::SpirvShader,
67 QShaderVersion(100)));
68 QVERIFY(!shader.shader().isEmpty());
69 QCOMPARE(shader.entryPoint(), QByteArrayLiteral("main"));
70
71 const QShaderDescription desc = s.description();
72 QVERIFY(desc.isValid());
73 QCOMPARE(desc.inputVariables().count(), 2);
74 for (const QShaderDescription::InOutVariable &v : desc.inputVariables()) {
75 switch (v.location) {
76 case 0:
77 QCOMPARE(v.name, QLatin1String("position"));
78 QCOMPARE(v.type, QShaderDescription::Vec4);
79 break;
80 case 1:
81 QCOMPARE(v.name, QLatin1String("color"));
82 QCOMPARE(v.type, QShaderDescription::Vec3);
83 break;
84 default:
85 QVERIFY(false);
86 break;
87 }
88 }
89 QCOMPARE(desc.outputVariables().count(), 1);
90 for (const QShaderDescription::InOutVariable &v : desc.outputVariables()) {
91 switch (v.location) {
92 case 0:
93 QCOMPARE(v.name, QLatin1String("v_color"));
94 QCOMPARE(v.type, QShaderDescription::Vec3);
95 break;
96 default:
97 QVERIFY(false);
98 break;
99 }
100 }
101 QCOMPARE(desc.uniformBlocks().count(), 1);
102 const QShaderDescription::UniformBlock blk = desc.uniformBlocks().first();
103 QCOMPARE(blk.blockName, QLatin1String("buf"));
104 QCOMPARE(blk.structName, QLatin1String("ubuf"));
105 QCOMPARE(blk.size, 68);
106 QCOMPARE(blk.binding, 0);
107 QCOMPARE(blk.descriptorSet, 0);
108 QCOMPARE(blk.members.count(), 2);
109 for (int i = 0; i < blk.members.count(); ++i) {
110 const QShaderDescription::BlockVariable v = blk.members[i];
111 switch (i) {
112 case 0:
113 QCOMPARE(v.offset, 0);
114 QCOMPARE(v.size, 64);
115 QCOMPARE(v.name, QLatin1String("mvp"));
116 QCOMPARE(v.type, QShaderDescription::Mat4);
117 QCOMPARE(v.matrixStride, 16);
118 break;
119 case 1:
120 QCOMPARE(v.offset, 64);
121 QCOMPARE(v.size, 4);
122 QCOMPARE(v.name, QLatin1String("opacity"));
123 QCOMPARE(v.type, QShaderDescription::Float);
124 break;
125 default:
126 QVERIFY(false);
127 break;
128 }
129 }
130}
131
132void tst_QShader::genVariants()
133{
134 QShader s = getShader(name: QLatin1String(":/data/color_all_v1.vert.qsb"));
135 // spirv, glsl 100, glsl 330, glsl 120, hlsl 50, msl 12
136 // + batchable variants
137 QVERIFY(s.isValid());
138 QCOMPARE(QShaderPrivate::get(&s)->qsbVersion, 1);
139 QCOMPARE(s.availableShaders().count(), 2 * 6);
140
141 int batchableVariantCount = 0;
142 int batchableGlslVariantCount = 0;
143 for (const QShaderKey &key : s.availableShaders()) {
144 if (key.sourceVariant() == QShader::BatchableVertexShader) {
145 ++batchableVariantCount;
146 if (key.source() == QShader::GlslShader) {
147 ++batchableGlslVariantCount;
148 const QByteArray src = s.shader(key).shader();
149 QVERIFY(src.contains(QByteArrayLiteral("_qt_order * ")));
150 }
151 }
152 }
153 QCOMPARE(batchableVariantCount, 6);
154 QCOMPARE(batchableGlslVariantCount, 3);
155}
156
157void tst_QShader::shaderDescImplicitSharing()
158{
159 QShader s = getShader(name: QLatin1String(":/data/color_spirv_v1.vert.qsb"));
160 QVERIFY(s.isValid());
161 QCOMPARE(QShaderPrivate::get(&s)->qsbVersion, 1);
162 QCOMPARE(s.availableShaders().count(), 1);
163 QVERIFY(s.availableShaders().contains(QShaderKey(QShader::SpirvShader, QShaderVersion(100))));
164
165 QShaderDescription d0 = s.description();
166 QVERIFY(d0.isValid());
167 QCOMPARE(d0.inputVariables().count(), 2);
168 QCOMPARE(d0.outputVariables().count(), 1);
169 QCOMPARE(d0.uniformBlocks().count(), 1);
170
171 QShaderDescription d1 = d0;
172 QVERIFY(QShaderDescriptionPrivate::get(&d0) == QShaderDescriptionPrivate::get(&d1));
173 QCOMPARE(d0.inputVariables().count(), 2);
174 QCOMPARE(d0.outputVariables().count(), 1);
175 QCOMPARE(d0.uniformBlocks().count(), 1);
176 QCOMPARE(d1.inputVariables().count(), 2);
177 QCOMPARE(d1.outputVariables().count(), 1);
178 QCOMPARE(d1.uniformBlocks().count(), 1);
179 QCOMPARE(d0, d1);
180
181 d1.detach();
182 QVERIFY(QShaderDescriptionPrivate::get(&d0) != QShaderDescriptionPrivate::get(&d1));
183 QCOMPARE(d0.inputVariables().count(), 2);
184 QCOMPARE(d0.outputVariables().count(), 1);
185 QCOMPARE(d0.uniformBlocks().count(), 1);
186 QCOMPARE(d1.inputVariables().count(), 2);
187 QCOMPARE(d1.outputVariables().count(), 1);
188 QCOMPARE(d1.uniformBlocks().count(), 1);
189 QCOMPARE(d0, d1);
190
191 d1 = QShaderDescription();
192 QVERIFY(d0 != d1);
193}
194
195void tst_QShader::bakedShaderImplicitSharing()
196{
197 QShader s0 = getShader(name: QLatin1String(":/data/color_spirv_v1.vert.qsb"));
198 QVERIFY(s0.isValid());
199 QCOMPARE(QShaderPrivate::get(&s0)->qsbVersion, 1);
200 QCOMPARE(s0.availableShaders().count(), 1);
201 QVERIFY(s0.availableShaders().contains(QShaderKey(QShader::SpirvShader, QShaderVersion(100))));
202
203 {
204 QShader s1 = s0;
205 QVERIFY(QShaderPrivate::get(&s0) == QShaderPrivate::get(&s1));
206 QCOMPARE(s0.availableShaders().count(), 1);
207 QVERIFY(s0.availableShaders().contains(QShaderKey(QShader::SpirvShader, QShaderVersion(100))));
208 QCOMPARE(s1.availableShaders().count(), 1);
209 QVERIFY(s1.availableShaders().contains(QShaderKey(QShader::SpirvShader, QShaderVersion(100))));
210 QCOMPARE(s0.stage(), s1.stage());
211 QCOMPARE(s0, s1);
212
213 s1.detach();
214 QVERIFY(QShaderPrivate::get(&s0) != QShaderPrivate::get(&s1));
215 QCOMPARE(s0.availableShaders().count(), 1);
216 QVERIFY(s0.availableShaders().contains(QShaderKey(QShader::SpirvShader, QShaderVersion(100))));
217 QCOMPARE(s1.availableShaders().count(), 1);
218 QVERIFY(s1.availableShaders().contains(QShaderKey(QShader::SpirvShader, QShaderVersion(100))));
219 QCOMPARE(s0.stage(), s1.stage());
220 QCOMPARE(s0, s1);
221 }
222
223 {
224 QShader s1 = s0;
225 QVERIFY(QShaderPrivate::get(&s0) == QShaderPrivate::get(&s1));
226 QCOMPARE(s0.stage(), s1.stage());
227
228 s1.setStage(QShader::FragmentStage); // call a setter to trigger a detach
229 QVERIFY(QShaderPrivate::get(&s0) != QShaderPrivate::get(&s1));
230 QCOMPARE(s0.availableShaders().count(), 1);
231 QVERIFY(s0.availableShaders().contains(QShaderKey(QShader::SpirvShader, QShaderVersion(100))));
232 QCOMPARE(s1.availableShaders().count(), 1);
233 QVERIFY(s1.availableShaders().contains(QShaderKey(QShader::SpirvShader, QShaderVersion(100))));
234 QShaderDescription d0 = s0.description();
235 QCOMPARE(d0.inputVariables().count(), 2);
236 QCOMPARE(d0.outputVariables().count(), 1);
237 QCOMPARE(d0.uniformBlocks().count(), 1);
238 QShaderDescription d1 = s1.description();
239 QCOMPARE(d1.inputVariables().count(), 2);
240 QCOMPARE(d1.outputVariables().count(), 1);
241 QCOMPARE(d1.uniformBlocks().count(), 1);
242 QVERIFY(s0 != s1);
243 }
244}
245
246void tst_QShader::mslResourceMapping()
247{
248 QShader s = getShader(name: QLatin1String(":/data/texture_all_v2.frag.qsb"));
249 QVERIFY(s.isValid());
250 QCOMPARE(QShaderPrivate::get(&s)->qsbVersion, 2);
251
252 const QVector<QShaderKey> availableShaders = s.availableShaders();
253 QCOMPARE(availableShaders.count(), 7);
254 QVERIFY(availableShaders.contains(QShaderKey(QShader::SpirvShader, QShaderVersion(100))));
255 QVERIFY(availableShaders.contains(QShaderKey(QShader::MslShader, QShaderVersion(12))));
256 QVERIFY(availableShaders.contains(QShaderKey(QShader::HlslShader, QShaderVersion(50))));
257 QVERIFY(availableShaders.contains(QShaderKey(QShader::GlslShader, QShaderVersion(100, QShaderVersion::GlslEs))));
258 QVERIFY(availableShaders.contains(QShaderKey(QShader::GlslShader, QShaderVersion(120))));
259 QVERIFY(availableShaders.contains(QShaderKey(QShader::GlslShader, QShaderVersion(150))));
260 QVERIFY(availableShaders.contains(QShaderKey(QShader::GlslShader, QShaderVersion(330))));
261
262 const QShader::NativeResourceBindingMap *resMap =
263 s.nativeResourceBindingMap(key: QShaderKey(QShader::GlslShader, QShaderVersion(330)));
264 QVERIFY(!resMap);
265
266 // The Metal shader must come with a mapping table for binding points 0
267 // (uniform buffer) and 1 (combined image sampler mapped to a texture and
268 // sampler in the shader).
269 resMap = s.nativeResourceBindingMap(key: QShaderKey(QShader::MslShader, QShaderVersion(12)));
270 QVERIFY(resMap);
271
272 QCOMPARE(resMap->count(), 2);
273 QCOMPARE(resMap->value(0).first, 0); // mapped to native buffer index 0
274 QCOMPARE(resMap->value(1), qMakePair(0, 0)); // mapped to native texture index 0 and sampler index 0
275}
276
277void tst_QShader::loadV3()
278{
279 // qsb version 3: QShaderDescription is serialized as CBOR. Ensure the deserialized data is as expected.
280 QShader s = getShader(name: QLatin1String(":/data/texture_all_v3.frag.qsb"));
281 QVERIFY(s.isValid());
282 QCOMPARE(QShaderPrivate::get(&s)->qsbVersion, 3);
283
284 const QVector<QShaderKey> availableShaders = s.availableShaders();
285 QCOMPARE(availableShaders.count(), 7);
286 QVERIFY(availableShaders.contains(QShaderKey(QShader::SpirvShader, QShaderVersion(100))));
287 QVERIFY(availableShaders.contains(QShaderKey(QShader::MslShader, QShaderVersion(12))));
288 QVERIFY(availableShaders.contains(QShaderKey(QShader::HlslShader, QShaderVersion(50))));
289 QVERIFY(availableShaders.contains(QShaderKey(QShader::GlslShader, QShaderVersion(100, QShaderVersion::GlslEs))));
290 QVERIFY(availableShaders.contains(QShaderKey(QShader::GlslShader, QShaderVersion(120))));
291 QVERIFY(availableShaders.contains(QShaderKey(QShader::GlslShader, QShaderVersion(150))));
292 QVERIFY(availableShaders.contains(QShaderKey(QShader::GlslShader, QShaderVersion(330))));
293
294 const QShaderDescription desc = s.description();
295 QVERIFY(desc.isValid());
296 QCOMPARE(desc.inputVariables().count(), 1);
297 for (const QShaderDescription::InOutVariable &v : desc.inputVariables()) {
298 switch (v.location) {
299 case 0:
300 QCOMPARE(v.name, QLatin1String("qt_TexCoord"));
301 QCOMPARE(v.type, QShaderDescription::Vec2);
302 break;
303 default:
304 QVERIFY(false);
305 break;
306 }
307 }
308 QCOMPARE(desc.outputVariables().count(), 1);
309 for (const QShaderDescription::InOutVariable &v : desc.outputVariables()) {
310 switch (v.location) {
311 case 0:
312 QCOMPARE(v.name, QLatin1String("fragColor"));
313 QCOMPARE(v.type, QShaderDescription::Vec4);
314 break;
315 default:
316 QVERIFY(false);
317 break;
318 }
319 }
320 QCOMPARE(desc.uniformBlocks().count(), 1);
321 const QShaderDescription::UniformBlock blk = desc.uniformBlocks().first();
322 QCOMPARE(blk.blockName, QLatin1String("buf"));
323 QCOMPARE(blk.structName, QLatin1String("ubuf"));
324 QCOMPARE(blk.size, 68);
325 QCOMPARE(blk.binding, 0);
326 QCOMPARE(blk.descriptorSet, 0);
327 QCOMPARE(blk.members.count(), 2);
328 for (int i = 0; i < blk.members.count(); ++i) {
329 const QShaderDescription::BlockVariable v = blk.members[i];
330 switch (i) {
331 case 0:
332 QCOMPARE(v.offset, 0);
333 QCOMPARE(v.size, 64);
334 QCOMPARE(v.name, QLatin1String("qt_Matrix"));
335 QCOMPARE(v.type, QShaderDescription::Mat4);
336 QCOMPARE(v.matrixStride, 16);
337 break;
338 case 1:
339 QCOMPARE(v.offset, 64);
340 QCOMPARE(v.size, 4);
341 QCOMPARE(v.name, QLatin1String("opacity"));
342 QCOMPARE(v.type, QShaderDescription::Float);
343 break;
344 default:
345 QVERIFY(false);
346 break;
347 }
348 }
349}
350
351void tst_QShader::serializeShaderDesc()
352{
353 // default constructed QShaderDescription
354 {
355 QShaderDescription desc;
356 QVERIFY(!desc.isValid());
357
358 QByteArray data;
359 {
360 QBuffer buf(&data);
361 QDataStream ds(&buf);
362 QVERIFY(buf.open(QIODevice::WriteOnly));
363 desc.serialize(stream: &ds);
364 }
365 QVERIFY(!data.isEmpty());
366
367 {
368 QBuffer buf(&data);
369 QDataStream ds(&buf);
370 QVERIFY(buf.open(QIODevice::ReadOnly));
371 QShaderDescription desc2 = QShaderDescription::deserialize(stream: &ds, version: QShaderPrivate::QSB_VERSION);
372 QVERIFY(!desc2.isValid());
373 }
374 }
375
376 // a QShaderDescription with inputs, outputs, uniform block and combined image sampler
377 {
378 QShader s = getShader(name: QLatin1String(":/data/texture_all_v4.frag.qsb"));
379 QVERIFY(s.isValid());
380 const QShaderDescription desc = s.description();
381 QVERIFY(desc.isValid());
382
383 QByteArray data;
384 {
385 QBuffer buf(&data);
386 QDataStream ds(&buf);
387 QVERIFY(buf.open(QIODevice::WriteOnly));
388 desc.serialize(stream: &ds);
389 }
390 QVERIFY(!data.isEmpty());
391
392 {
393 QShaderDescription desc2;
394 QVERIFY(!desc2.isValid());
395 QVERIFY(!(desc == desc2));
396 QVERIFY(desc != desc2);
397 }
398
399 {
400 QBuffer buf(&data);
401 QDataStream ds(&buf);
402 QVERIFY(buf.open(QIODevice::ReadOnly));
403 QShaderDescription desc2 = QShaderDescription::deserialize(stream: &ds, version: QShaderPrivate::QSB_VERSION);
404 QVERIFY(desc2.isValid());
405 QCOMPARE(desc, desc2);
406 }
407 }
408}
409
410void tst_QShader::comparison()
411{
412 // exercise QShader and QShaderDescription comparisons
413 {
414 QShader s1 = getShader(name: QLatin1String(":/data/texture_all_v4.frag.qsb"));
415 QVERIFY(s1.isValid());
416 QShader s2 = getShader(name: QLatin1String(":/data/color_all_v1.vert.qsb"));
417 QVERIFY(s2.isValid());
418
419 QVERIFY(s1.description().isValid());
420 QVERIFY(s2.description().isValid());
421
422 QVERIFY(s1 != s2);
423 QVERIFY(s1.description() != s2.description());
424 }
425
426 {
427 QShader s1 = getShader(name: QLatin1String(":/data/texture_all_v4.frag.qsb"));
428 QVERIFY(s1.isValid());
429 QShader s2 = getShader(name: QLatin1String(":/data/texture_all_v4.frag.qsb"));
430 QVERIFY(s2.isValid());
431
432 QVERIFY(s1.description().isValid());
433 QVERIFY(s2.description().isValid());
434
435 QVERIFY(s1 == s2);
436 QVERIFY(s1.description() == s2.description());
437 }
438}
439
440void tst_QShader::loadV4()
441{
442 // qsb version 4: QShaderDescription is serialized via QDataStream. Ensure the deserialized data is as expected.
443 QShader s = getShader(name: QLatin1String(":/data/texture_all_v4.frag.qsb"));
444 QVERIFY(s.isValid());
445 QCOMPARE(QShaderPrivate::get(&s)->qsbVersion, 4);
446
447 const QVector<QShaderKey> availableShaders = s.availableShaders();
448 QCOMPARE(availableShaders.count(), 7);
449 QVERIFY(availableShaders.contains(QShaderKey(QShader::SpirvShader, QShaderVersion(100))));
450 QVERIFY(availableShaders.contains(QShaderKey(QShader::MslShader, QShaderVersion(12))));
451 QVERIFY(availableShaders.contains(QShaderKey(QShader::HlslShader, QShaderVersion(50))));
452 QVERIFY(availableShaders.contains(QShaderKey(QShader::GlslShader, QShaderVersion(100, QShaderVersion::GlslEs))));
453 QVERIFY(availableShaders.contains(QShaderKey(QShader::GlslShader, QShaderVersion(120))));
454 QVERIFY(availableShaders.contains(QShaderKey(QShader::GlslShader, QShaderVersion(150))));
455 QVERIFY(availableShaders.contains(QShaderKey(QShader::GlslShader, QShaderVersion(330))));
456
457 const QShaderDescription desc = s.description();
458 QVERIFY(desc.isValid());
459 QCOMPARE(desc.inputVariables().count(), 1);
460 for (const QShaderDescription::InOutVariable &v : desc.inputVariables()) {
461 switch (v.location) {
462 case 0:
463 QCOMPARE(v.name, QLatin1String("qt_TexCoord"));
464 QCOMPARE(v.type, QShaderDescription::Vec2);
465 break;
466 default:
467 QVERIFY(false);
468 break;
469 }
470 }
471 QCOMPARE(desc.outputVariables().count(), 1);
472 for (const QShaderDescription::InOutVariable &v : desc.outputVariables()) {
473 switch (v.location) {
474 case 0:
475 QCOMPARE(v.name, QLatin1String("fragColor"));
476 QCOMPARE(v.type, QShaderDescription::Vec4);
477 break;
478 default:
479 QVERIFY(false);
480 break;
481 }
482 }
483 QCOMPARE(desc.uniformBlocks().count(), 1);
484 const QShaderDescription::UniformBlock blk = desc.uniformBlocks().first();
485 QCOMPARE(blk.blockName, QLatin1String("buf"));
486 QCOMPARE(blk.structName, QLatin1String("ubuf"));
487 QCOMPARE(blk.size, 68);
488 QCOMPARE(blk.binding, 0);
489 QCOMPARE(blk.descriptorSet, 0);
490 QCOMPARE(blk.members.count(), 2);
491 for (int i = 0; i < blk.members.count(); ++i) {
492 const QShaderDescription::BlockVariable v = blk.members[i];
493 switch (i) {
494 case 0:
495 QCOMPARE(v.offset, 0);
496 QCOMPARE(v.size, 64);
497 QCOMPARE(v.name, QLatin1String("qt_Matrix"));
498 QCOMPARE(v.type, QShaderDescription::Mat4);
499 QCOMPARE(v.matrixStride, 16);
500 break;
501 case 1:
502 QCOMPARE(v.offset, 64);
503 QCOMPARE(v.size, 4);
504 QCOMPARE(v.name, QLatin1String("opacity"));
505 QCOMPARE(v.type, QShaderDescription::Float);
506 break;
507 default:
508 QVERIFY(false);
509 break;
510 }
511 }
512}
513
514#include <tst_qshader.moc>
515QTEST_MAIN(tst_QShader)
516

source code of qtbase/tests/auto/gui/rhi/qshader/tst_qshader.cpp