1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qshaderbaker.h"
5#include "qspirvcompiler_p.h"
6#include "qspirvshader_p.h"
7#include <QFileInfo>
8#include <QFile>
9#include <QDebug>
10
11QT_BEGIN_NAMESPACE
12
13/*!
14 \class QShaderBaker
15 \inmodule QtShaderTools
16 \since 6.6
17
18 \brief Compiles a GLSL/Vulkan shader into SPIR-V, translates into other
19 shading languages, and gathers reflection metadata.
20
21 \warning QShaderBaker, just like the QRhi family of classes in the Qt Gui
22 module, including QShader and QShaderDescription, offers limited
23 compatibility guarantees. There are no source or binary compatibility
24 guarantees for these classes, meaning the API is only guaranteed to work
25 with the Qt version the application was developed against. Source
26 incompatible changes are however aimed to be kept at a minimum and will only
27 be made in minor releases (6.7, 6.8, and so on). To use this class in an
28 application, link to \c{Qt::ShaderToolsPrivate} (if using CMake), and
29 include the headers with the \c rhi prefix, for example
30 \c{#include <rhi/qshaderbaker.h>}.
31
32 QShaderBaker takes a graphics (vertex, fragment, etc.) or compute shader,
33 and produces multiple - either source or bytecode - variants of it,
34 together with reflection information. The results are represented by a
35 QShader instance, which also provides simple and fast serialization
36 and deserialization.
37
38 \note Applications and libraries are recommended to avoid using this class
39 directly. Rather, all Qt users are encouraged to rely on offline compilation
40 by invoking the \c qsb command-line tool at build time via CMake. The \c qsb
41 tool uses QShaderBaker and writes the serialized version of the generated
42 QShader into a file. The usage of this class should be restricted to cases
43 where run time compilation cannot be avoided, such as when working with
44 user-provided or dynamically generated shader source strings.
45
46 The input format is always assumed to be Vulkan-flavored GLSL at the
47 moment. See the
48 \l{https://github.com/KhronosGroup/GLSL/blob/master/extensions/khr/GL_KHR_vulkan_glsl.txt}{GL_KHR_vulkan_glsl
49 specification} for an overview, keeping in mind that the Qt Shader Tools
50 module is meant to be used in combination with the QRhi classes from Qt
51 Rendering Hardware Interface module, and therefore a number of concepts and
52 constructs (push constants, storage buffers, subpasses, etc.) are not
53 applicable at the moment. Additional options may be introduced in the
54 future, for example, by enabling
55 \l{https://docs.microsoft.com/en-us/windows/desktop/direct3dhlsl/dx-graphics-hlsl}{HLSL}
56 as a source format, once HLSL to SPIR-V compilation is deemed suitable.
57
58 The reflection metadata is retrievable from the resulting QShader by
59 calling QShader::description(). This is essential when having to
60 discover what set of vertex inputs and shader resources a shader expects,
61 and what the layouts of those are, as many modern graphics APIs offer no
62 built-in shader reflection capabilities.
63
64 \section2 Typical Workflow
65
66 Let's assume an application has a vertex and fragment shader like the following:
67
68 Vertex shader:
69 \snippet color.vert 0
70
71 Fragment shader:
72 \snippet color.frag 0
73
74 To get QShader instances that can be passed as-is to a
75 QRhiGraphicsPipeline, there are two options: doing the shader pack
76 generation off line, or at run time.
77
78 The former involves running the \c qsb tool:
79
80 \badcode
81 qsb --glsl "100 es,120" --hlsl 50 --msl 12 color.vert -o color.vert.qsb
82 qsb --glsl "100 es,120" --hlsl 50 --msl 12 color.frag -o color.frag.qsb
83 \endcode
84
85 The example uses the translation targets as appropriate for QRhi. This
86 means GLSL/ES 100, GLSL 120, HLSL Shader Model 5.0, and Metal Shading
87 Language 1.2.
88
89 Note how the command line options correspond to what can be specified via
90 setGeneratedShaders(). Once the resulting files are available, they can be
91 shipped with the application (typically embedded into the executable the
92 the Qt Resource System), and can be loaded and passed to
93 QShader::fromSerialized() at run time.
94
95 While not shown here, \c qsb can do more: it is also able to invoke \c fxc
96 on Windows or the appropriate XCode tools on macOS to compile the generated
97 HLSL or Metal shader code into bytecode and include the compiled versions
98 in the QShader. After a baked shader pack is written into a file, its
99 contents can be examined by running \c{qsb -d} on it. Run \c qsb with
100 \c{--help} for more information.
101
102 The alternative approach is to perform the same at run time. This involves
103 creating a QShaderBaker instance, calling setSourceFileName(), and then
104 setting up the translation targets via setGeneratedShaders():
105
106 \badcode
107 baker.setGeneratedShaderVariants({ QShader::StandardShader });
108 QList<QShaderBaker::GeneratedShader> targets;
109 targets.append({ QShader::SpirvShader, QShaderVersion(100) });
110 targets.append({ QShader::GlslShader, QShaderVersion(100, QShaderVersion::GlslEs) });
111 targets.append({ QShader::SpirvShader, QShaderVersion(120) });
112 targets.append({ QShader::HlslShader, QShaderVersion(50) });
113 targets.append({ QShader::MslShader, QShaderVersion(12) });
114 baker.setGeneratedShaders(targets);
115 QShader shaders = baker.bake();
116 if (!shaders.isValid())
117 qWarning() << baker.errorMessage();
118 \endcode
119
120 \sa QShader
121 */
122
123struct QShaderBakerPrivate
124{
125 bool readFile(const QString &fn);
126 QPair<QByteArray, QByteArray> compile();
127 QByteArray perTargetDefines(const QShaderBaker::GeneratedShader &key);
128
129 QString sourceFileName;
130 QByteArray source;
131 QShader::Stage stage;
132 QList<QShaderBaker::GeneratedShader> reqVersions;
133 QList<QShader::Variant> variants;
134 QByteArray preamble;
135 int batchLoc = 7;
136 bool perTargetEnabled = false;
137 bool breakOnShaderTranslationError = true;
138 QSpirvShader::TessellationInfo tessInfo;
139 QShaderBaker::SpirvOptions spirvOptions;
140 QSpirvCompiler compiler;
141 QString errorMessage;
142};
143
144bool QShaderBakerPrivate::readFile(const QString &fn)
145{
146 QFile f(fn);
147 if (!f.open(flags: QIODevice::ReadOnly | QIODevice::Text)) {
148 qWarning(msg: "QShaderBaker: Failed to open %s", qPrintable(fn));
149 return false;
150 }
151 source = f.readAll();
152 sourceFileName = fn;
153 return true;
154}
155
156/*!
157 Constructs a new QShaderBaker.
158 */
159QShaderBaker::QShaderBaker()
160 : d(new QShaderBakerPrivate)
161{
162}
163
164/*!
165 Destructor.
166 */
167QShaderBaker::~QShaderBaker()
168{
169 delete d;
170}
171
172/*!
173 Sets the name of the shader source file to \a fileName. This is the file
174 that will be read when calling bake(). The shader stage is deduced
175 automatically from the file extension. When this is not desired or not
176 possible, use the overload with the stage argument instead.
177
178 The supported file extensions are:
179 \list
180 \li \c{.vert} - vertex shader
181 \li \c{.frag} - fragment (pixel) shader
182 \li \c{.tesc} - tessellation control (hull) shader
183 \li \c{.tese} - tessellation evaluation (domain) shader
184 \li \c{.geom} - geometry shader
185 \li \c{.comp} - compute shader
186 \endlist
187 */
188void QShaderBaker::setSourceFileName(const QString &fileName)
189{
190 if (!d->readFile(fn: fileName))
191 return;
192
193 const QString suffix = QFileInfo(fileName).suffix();
194 if (suffix == QStringLiteral("vert")) {
195 d->stage = QShader::VertexStage;
196 } else if (suffix == QStringLiteral("frag")) {
197 d->stage = QShader::FragmentStage;
198 } else if (suffix == QStringLiteral("tesc")) {
199 d->stage = QShader::TessellationControlStage;
200 } else if (suffix == QStringLiteral("tese")) {
201 d->stage = QShader::TessellationEvaluationStage;
202 } else if (suffix == QStringLiteral("geom")) {
203 d->stage = QShader::GeometryStage;
204 } else if (suffix == QStringLiteral("comp")) {
205 d->stage = QShader::ComputeStage;
206 } else {
207 qWarning(msg: "QShaderBaker: Unknown shader stage, defaulting to vertex");
208 d->stage = QShader::VertexStage;
209 }
210}
211
212/*!
213 Sets the name of the shader source file to \a fileName. This is the file
214 that will be read when calling bake(). The shader stage is specified by \a
215 stage.
216 */
217void QShaderBaker::setSourceFileName(const QString &fileName, QShader::Stage stage)
218{
219 if (d->readFile(fn: fileName))
220 d->stage = stage;
221}
222
223/*!
224 Sets the source \a device. This allows using any QIODevice instead of just
225 files. \a stage specifies the shader stage, while the optional \a fileName
226 contains a filename that is used in the error messages.
227 */
228void QShaderBaker::setSourceDevice(QIODevice *device, QShader::Stage stage, const QString &fileName)
229{
230 setSourceString(sourceString: device->readAll(), stage, fileName);
231}
232
233/*!
234 Sets the input shader \a sourceString. \a stage specified the shader stage,
235 while the optional \a fileName contains a filename that is used in the
236 error messages.
237 */
238void QShaderBaker::setSourceString(const QByteArray &sourceString, QShader::Stage stage, const QString &fileName)
239{
240 d->sourceFileName = fileName; // for error messages, include handling, etc.
241 d->source = sourceString;
242 d->stage = stage;
243}
244
245/*!
246 \typedef QShaderBaker::GeneratedShader
247
248 Synonym for QPair<QShader::Source, QShaderVersion>.
249*/
250
251/*!
252 Specifies what kind of shaders to compile or translate to. Nothing is
253 generated by default so calling this function before bake() is mandatory
254
255 \note when this function is not called or \a v is empty or contains only invalid
256 entries, the resulting QShader will be empty and thus invalid.
257
258 For example, the minimal possible baking target is SPIR-V, without any
259 additional translations to other languages. To request this, do:
260
261 \badcode
262 baker.setGeneratedShaders({ QShader::SpirvShader, QShaderVersion(100) });
263 \endcode
264
265 \note QShaderBaker only handles the SPIR-V and human-readable source
266 targets. Further compilation into API-specific intermediate formats, such
267 as QShader::DxbcShader or QShader::MetalLibShader is implemented by the
268 \c qsb command-line tool, and is not part of the QShaderBaker runtime API.
269 */
270void QShaderBaker::setGeneratedShaders(const QList<GeneratedShader> &v)
271{
272 d->reqVersions = v;
273}
274
275/*!
276 Specifies which shader variants are generated. Each shader version can have
277 multiple variants in the resulting QShader.
278
279 In most cases \a v contains a single entry, QShader::StandardShader.
280
281 \note when no variants are set, the resulting QShader will be empty and
282 thus invalid.
283 */
284void QShaderBaker::setGeneratedShaderVariants(const QList<QShader::Variant> &v)
285{
286 d->variants = v;
287}
288
289/*!
290 Specifies a custom \a preamble that is processed before the normal shader
291 code.
292
293 This is more than just prepending to the source string: the validity of the
294 GLSL version directive, which is required to be placed before everything
295 else, is not affected. Line numbers in the reported error messages also
296 remain unchanged, ignoring the contents given in the \a preamble.
297
298 One use case for preambles is to transparently insert dynamically generated
299 \c{#define} statements.
300 */
301void QShaderBaker::setPreamble(const QByteArray &preamble)
302{
303 d->preamble = preamble;
304}
305
306/*!
307 When generating a QShader::BatchableVertexShader variant, \a location
308 specifies the input location for the inserted vertex input. The value is by
309 default 7 and needs to be overridden only if the vertex shader already uses
310 input location 7.
311 */
312void QShaderBaker::setBatchableVertexShaderExtraInputLocation(int location)
313{
314 d->batchLoc = location;
315}
316
317/*!
318 Sets per-target compilation to \a enable. By default this is disabled,
319 meaning that the Vulkan/GLSL source is compiled to SPIR-V once per variant.
320 (so once by default, twice if it is a vertex shader and the Batchable
321 variant as requested as well). The resulting SPIR-V is then translated to
322 the various target languages (GLSL, HLSL, MSL).
323
324 In per-target compilation mode, there is a separate GLSL to SPIR-V
325 compilation step for each target, meaning for each GLSL/HLSL/MSL version
326 requested via setGeneratedShaders(). The input source is the same, but with
327 target-specific preprocessor defines inserted. This is significantly more
328 time consuming, but allows applications to provide a single shader and use
329 \c{#ifdef} blocks to differentiate. When this mode is disabled, the only
330 way to achieve the same is to provide multiple versions of the shader file,
331 process each separately, ship {.qsb} files for each, and choose the right
332 file based on run time logic.
333
334 The following macros will be automatically defined in this mode. Note that
335 the macros are always tied to shading languages, not graphics APIs.
336
337 \list
338
339 \li \c{QSHADER_SPIRV} - defined when targeting SPIR-V (to be consumed,
340 typically, by Vulkan).
341
342 \li \c{QSHADER_SPIRV_VERSION} - the targeted SPIR-V version number, such as
343 \c 100.
344
345 \li \c{QSHADER_GLSL} - defined when targeting GLSL or GLSL ES (to be
346 consumed, typically, by OpenGL or OpenGL ES)
347
348 \li \c{QSHADER_GLSL_VERSION} - the targeted GLSL or GLSL ES version number,
349 such as \c 100, \c 300, or \c 330.
350
351 \li \c{QSHADER_GLSL_ES} - defined only when targeting GLSL ES
352
353 \li \c{QSHADER_HLSL} - defined when targeting HLSL (to be consumed,
354 typically, by Direct 3D)
355
356 \li \c{QSHADER_HLSL_VERSION} - the targeted HLSL shader model version, such
357 as \c 50
358
359 \li \c{QSHADER_MSL} - defined when targeting the Metal Shading Language (to
360 be consumed, typically, by Metal)
361
362 \li \c{QSHADER_MSL_VERSION} - the targeted MSL version, such as \c 12 or
363 \c 20.
364
365 \endlist
366
367 This allows writing shader code like the following.
368
369 \badcode
370 #if QSHADER_HLSL || QSHADER_MSL
371 vec2 uv = vec2(uv_coord.x, 1.0 - uv_coord.y);
372 #else
373 vec2 uv = uv_coord;
374 #endif
375 \endcode
376
377 \note Version numbers follow the GLSL-inspired QShaderVersion syntax and
378 thus are a single integer always.
379
380 \note There is only one QShaderDescription per QShader, no matter how many
381 individual targets there are. Therefore members of uniform blocks, vertex
382 inputs, etc. must not be made conditional using the macros described above.
383
384 \warning Be aware of the differences between the concepts of graphics APIs
385 and shading languages. QShaderBaker and the related tools work strictly
386 with the concept of shading languages, ignoring how the results are
387 consumed afterwards. Therefore, if the higher layers in the Qt graphics
388 stack one day start using SPIR-V also for an API other than Vulkan, the
389 assumption that QSHADER_SPIRV implies Vulkan will no longer hold.
390 */
391void QShaderBaker::setPerTargetCompilation(bool enable)
392{
393 d->perTargetEnabled = enable;
394}
395
396/*!
397 Controls the behavior when shader translation (from SPIR-V to
398 GLSL/HLSL/MSL) fails. By default this setting is true, which will cause
399 bake() to return with an error if a requested shader cannot be generated.
400 If that is not desired, and the intention is to generate what we can but
401 silently skip the rest, then set \a enable to false.
402
403 Targeting multiple GLSL versions can lead to errors when a feature is not
404 translatable to a given version. For example, attempting to translate a
405 shader using textureSize() to GLSL ES 100 would fail the entire bake() call
406 with the error message "textureSize is not supported in ESSL 100". If it is
407 acceptable to not have a GLSL ES 100 shader in the result, even though it
408 was requested, then setting this flag to false makes bake() to succeed.
409 */
410void QShaderBaker::setBreakOnShaderTranslationError(bool enable)
411{
412 d->breakOnShaderTranslationError = enable;
413}
414
415/*!
416 When generating MSL shader code for a tessellation control shader, the
417 tessellation \a mode (triangles or quads) must be known upfront. In GLSL
418 this is declared in the tessellation evaluation shader typically, but for
419 Metal it must be known also when generating the compute shader from the
420 tessellation control shader.
421
422 When not set, the default is triangles.
423 */
424void QShaderBaker::setTessellationMode(QShaderDescription::TessellationMode mode)
425{
426 d->tessInfo.infoForTesc.mode = mode;
427}
428
429/*!
430 When generating MSL shader code for a tessellation evaluation shader, the
431 output vertex \a count of the tessellation control shader must be known
432 upfront. in GLSL this would be declared in the tessellation control shader
433 typically, but for Metal it must be known also when generating the vertex
434 shader from the teselation evaluation shader.
435
436 When not set, the default value is 3.
437 */
438void QShaderBaker::setTessellationOutputVertexCount(int count)
439{
440 d->tessInfo.infoForTese.vertexCount = count;
441}
442
443void QShaderBaker::setSpirvOptions(SpirvOptions options)
444{
445 d->spirvOptions = options;
446}
447
448inline size_t qHash(const QShaderBaker::GeneratedShader &k, size_t seed = 0)
449{
450 return qHash(e: k.first, seed) ^ k.second.version();
451}
452
453QPair<QByteArray, QByteArray> QShaderBakerPrivate::compile()
454{
455 QSpirvCompiler::Flags flags;
456 if (spirvOptions.testFlag(flag: QShaderBaker::SpirvOption::GenerateFullDebugInfo))
457 flags |= QSpirvCompiler::FullDebugInfo;
458
459 compiler.setFlags(flags);
460 const QByteArray spirvBin = compiler.compileToSpirv();
461 if (spirvBin.isEmpty()) {
462 errorMessage = compiler.errorMessage();
463 return {};
464 }
465 QByteArray batchableSpirvBin;
466 if (stage == QShader::VertexStage && variants.contains(t: QShader::BatchableVertexShader)) {
467 compiler.setFlags(flags | QSpirvCompiler::RewriteToMakeBatchableForSG);
468 compiler.setSGBatchingVertexInputLocation(batchLoc);
469 batchableSpirvBin = compiler.compileToSpirv();
470 if (batchableSpirvBin.isEmpty()) {
471 errorMessage = compiler.errorMessage();
472 return {};
473 }
474 }
475 return { spirvBin, batchableSpirvBin };
476}
477
478QByteArray QShaderBakerPrivate::perTargetDefines(const QShaderBaker::GeneratedShader &key)
479{
480 QByteArray preamble;
481 switch (key.first) {
482 case QShader::SpirvShader:
483 preamble += QByteArrayLiteral("\n#define QSHADER_SPIRV 1\n#define QSHADER_SPIRV_VERSION ");
484 preamble += QByteArray::number(key.second.version());
485 preamble += QByteArrayLiteral("\n");
486 break;
487 case QShader::GlslShader:
488 preamble += QByteArrayLiteral("\n#define QSHADER_GLSL 1\n#define QSHADER_GLSL_VERSION ");
489 preamble += QByteArray::number(key.second.version());
490 if (key.second.flags().testFlag(flag: QShaderVersion::GlslEs))
491 preamble += QByteArrayLiteral("\n#define QSHADER_GLSL_ES 1");
492 preamble += QByteArrayLiteral("\n");
493 break;
494 case QShader::HlslShader:
495 preamble += QByteArrayLiteral("\n#define QSHADER_HLSL 1\n#define QSHADER_HLSL_VERSION ");
496 preamble += QByteArray::number(key.second.version());
497 preamble += QByteArrayLiteral("\n");
498 break;
499 case QShader::MslShader:
500 preamble += QByteArrayLiteral("\n#define QSHADER_MSL 1\n#define QSHADER_MSL_VERSION ");
501 preamble += QByteArray::number(key.second.version());
502 preamble += QByteArrayLiteral("\n");
503 break;
504 default:
505 Q_UNREACHABLE();
506 }
507 return preamble;
508}
509
510/*!
511 Runs the compilation and translation process.
512
513 \return a QShader instance. To check if the process was successful,
514 call QShader::isValid(). When that indicates \c false, call
515 errorMessage() to retrieve the log.
516
517 This is an expensive operation. When calling this from applications, it can
518 be advisable to do it on a separate thread.
519
520 \note QShaderBaker instances are reusable: after calling bake(), the same
521 instance can be used with different inputs again. However, a QShaderBaker
522 instance should only be used on one single thread during its lifetime.
523 */
524QShader QShaderBaker::bake()
525{
526 d->errorMessage.clear();
527
528 if (d->source.isEmpty()) {
529 d->errorMessage = QLatin1String("QShaderBaker: No source specified");
530 return QShader();
531 }
532
533 d->compiler.setSourceString(sourceString: d->source, stage: d->stage, fileName: d->sourceFileName);
534
535 // Normally one entry, for QShader::SpirvShader only. However, in
536 // compile-per-target mode there is a separate SPIR-V binary generated for
537 // each target (so for each GLSL/HLSL/MSL version requested).
538 QHash<GeneratedShader, QByteArray> spirv;
539 QHash<GeneratedShader, QByteArray> batchableSpirv;
540 const auto compileSpirvAndBatchable = [this, &spirv, &batchableSpirv](const GeneratedShader &key) {
541 const QPair<QByteArray, QByteArray> bin = d->compile();
542 if (bin.first.isEmpty())
543 return false;
544 spirv.insert(key, value: bin.first);
545 if (!bin.second.isEmpty())
546 batchableSpirv.insert(key, value: bin.second);
547 return true;
548 };
549
550 if (!d->perTargetEnabled) {
551 d->compiler.setPreamble(d->preamble);
552 if (!compileSpirvAndBatchable({ QShader::SpirvShader, {} }))
553 return QShader();
554 } else {
555 // per-target compilation. the value here comes from the varying
556 // preamble (and so preprocessor defines)
557 for (GeneratedShader req: d->reqVersions) {
558 d->compiler.setPreamble(d->preamble + d->perTargetDefines(key: req));
559 if (!compileSpirvAndBatchable(req))
560 return QShader();
561 }
562 }
563
564 // Now spirv, and, if in use, batchableSpirv, contain at least one,
565 // optionally more SPIR-V binaries.
566 Q_ASSERT(!spirv.isEmpty() && (d->perTargetEnabled || spirv.size() == 1));
567
568 QShader bs;
569 bs.setStage(d->stage);
570
571 QSpirvShader spirvShader;
572 QSpirvShader batchableSpirvShader;
573 // The QShaderDescription can be different for variants (we just have a
574 // hardcoded rule to pick one), but cannot differ for targets (in
575 // per-target mode, hence we can just pick the first SPIR-V binary and
576 // generate the reflection data based on that)
577 spirvShader.setSpirvBinary(spirv: spirv.constKeyValueBegin()->second, stage: d->stage);
578 if (batchableSpirv.isEmpty()) {
579 bs.setDescription(spirvShader.shaderDescription());
580 } else {
581 batchableSpirvShader.setSpirvBinary(spirv: batchableSpirv.constKeyValueBegin()->second, stage: d->stage);
582 // prefer the batchable's reflection info with _qt_order and such present
583 bs.setDescription(batchableSpirvShader.shaderDescription());
584 }
585
586 for (const GeneratedShader &req: d->reqVersions) {
587 for (const QShader::Variant &v : d->variants) {
588 if (d->stage != QShader::VertexStage) {
589 if (v == QShader::BatchableVertexShader
590 || v == QShader::UInt32IndexedVertexAsComputeShader
591 || v == QShader::UInt16IndexedVertexAsComputeShader
592 || v == QShader::NonIndexedVertexAsComputeShader)
593 {
594 continue;
595 }
596 }
597 if (req.first != QShader::MslShader && req.first != QShader::MetalLibShader) {
598 if (v == QShader::UInt32IndexedVertexAsComputeShader
599 || v == QShader::UInt16IndexedVertexAsComputeShader
600 || v == QShader::NonIndexedVertexAsComputeShader)
601 {
602 continue;
603 }
604 }
605
606 QSpirvShader *currentSpirvShader = nullptr;
607 if (d->perTargetEnabled) {
608 // This is expensive too, in addition to the multiple
609 // compilation rounds, but opting in to per-target mode is a
610 // careful, conscious choice (hopefully), so it's fine.
611 if (v == QShader::BatchableVertexShader)
612 batchableSpirvShader.setSpirvBinary(spirv: batchableSpirv[req], stage: d->stage);
613 else
614 spirvShader.setSpirvBinary(spirv: spirv[req], stage: d->stage);
615 }
616 if (v == QShader::BatchableVertexShader)
617 currentSpirvShader = &batchableSpirvShader;
618 else
619 currentSpirvShader = &spirvShader;
620 Q_ASSERT(currentSpirvShader);
621 Q_ASSERT(!currentSpirvShader->spirvBinary().isEmpty());
622 const QShaderKey key(req.first, req.second, v);
623 QShaderCode shader;
624 shader.setEntryPoint(QByteArrayLiteral("main"));
625 switch (req.first) {
626 case QShader::SpirvShader:
627 if (d->spirvOptions.testFlag(flag: QShaderBaker::SpirvOption::StripDebugAndVarInfo)) {
628 QString errorMsg;
629 const QByteArray strippedSpirv = currentSpirvShader->remappedSpirvBinary(flags: QSpirvShader::RemapFlag::StripOnly, errorMessage: &errorMsg);
630 if (strippedSpirv.isEmpty()) {
631 d->errorMessage = errorMsg;
632 return QShader();
633 }
634 shader.setShader(strippedSpirv);
635 } else {
636 shader.setShader(currentSpirvShader->spirvBinary());
637 }
638 break;
639 case QShader::GlslShader:
640 {
641 QSpirvShader::GlslFlags flags;
642 if (req.second.flags().testFlag(flag: QShaderVersion::GlslEs))
643 flags |= QSpirvShader::GlslFlag::GlslEs;
644 QVector<QSpirvShader::SeparateToCombinedImageSamplerMapping> separateToCombinedImageSamplerMappings;
645 shader.setShader(currentSpirvShader->translateToGLSL(version: req.second.version(), flags, separateToCombinedImageSamplerMappings: &separateToCombinedImageSamplerMappings));
646 if (shader.shader().isEmpty()) {
647 if (d->breakOnShaderTranslationError) {
648 d->errorMessage = currentSpirvShader->translationErrorMessage();
649 return QShader();
650 } else {
651 d->errorMessage += QLatin1String(" ") + currentSpirvShader->translationErrorMessage();
652 continue;
653 }
654 }
655 if (!separateToCombinedImageSamplerMappings.isEmpty()) {
656 const QShaderDescription desc = bs.description();
657 QVector<QShaderDescription::InOutVariable> separateImages = desc.separateImages();
658 QVector<QShaderDescription::InOutVariable> separateSamplers = desc.separateSamplers();
659 QShader::SeparateToCombinedImageSamplerMappingList result;
660 for (const QSpirvShader::SeparateToCombinedImageSamplerMapping &mapping : separateToCombinedImageSamplerMappings) {
661 int textureBinding = -1;
662 int samplerBinding = -1;
663 for (int i = 0, count = separateImages.size(); i < count; ++i) {
664 if (separateImages[i].name == mapping.textureName) {
665 textureBinding = separateImages[i].binding;
666 break;
667 }
668 }
669 for (int i = 0, count = separateSamplers.size(); i < count; ++i) {
670 if (separateSamplers[i].name == mapping.samplerName) {
671 samplerBinding = separateSamplers[i].binding;
672 break;
673 }
674 }
675 result.append(t: { .combinedSamplerName: mapping.combinedSamplerName, .textureBinding: textureBinding, .samplerBinding: samplerBinding });
676 }
677 bs.setSeparateToCombinedImageSamplerMappingList(key, list: result);
678 }
679 }
680 break;
681 case QShader::HlslShader:
682 {
683 QShader::NativeResourceBindingMap nativeBindings;
684 shader.setShader(currentSpirvShader->translateToHLSL(version: req.second.version(), nativeBindings: &nativeBindings));
685 if (shader.shader().isEmpty()) {
686 if (d->breakOnShaderTranslationError) {
687 d->errorMessage = currentSpirvShader->translationErrorMessage();
688 return QShader();
689 } else {
690 d->errorMessage += QLatin1String(" ") + currentSpirvShader->translationErrorMessage();
691 continue;
692 }
693 }
694 bs.setResourceBindingMap(key, map: nativeBindings);
695 }
696 break;
697 case QShader::MslShader:
698 {
699 QShader::NativeResourceBindingMap nativeBindings;
700 QShader::NativeShaderInfo shaderInfo;
701 QSpirvShader::MslFlags flags;
702 if (d->stage == QShader::VertexStage) {
703 switch (v) {
704 case QShader::UInt16IndexedVertexAsComputeShader:
705 flags |= QSpirvShader::MslFlag::VertexAsCompute;
706 flags |= QSpirvShader::MslFlag::WithUInt16Index;
707 break;
708 case QShader::UInt32IndexedVertexAsComputeShader:
709 flags |= QSpirvShader::MslFlag::VertexAsCompute;
710 flags |= QSpirvShader::MslFlag::WithUInt32Index;
711 break;
712 case QShader::NonIndexedVertexAsComputeShader:
713 flags |= QSpirvShader::MslFlag::VertexAsCompute;
714 break;
715 default:
716 break;
717 }
718 }
719 shader.setShader(currentSpirvShader->translateToMSL(version: req.second.version(), flags, stage: d->stage, nativeBindings: &nativeBindings, shaderInfo: &shaderInfo, tessInfo: d->tessInfo));
720 if (shader.shader().isEmpty()) {
721 if (d->breakOnShaderTranslationError) {
722 d->errorMessage = currentSpirvShader->translationErrorMessage();
723 return QShader();
724 } else {
725 d->errorMessage += QLatin1String(" ") + currentSpirvShader->translationErrorMessage();
726 continue;
727 }
728 }
729 shader.setEntryPoint(QByteArrayLiteral("main0"));
730 bs.setResourceBindingMap(key, map: nativeBindings);
731 bs.setNativeShaderInfo(key, info: shaderInfo);
732 }
733 break;
734 default:
735 Q_UNREACHABLE();
736 }
737 bs.setShader(key, shader);
738 }
739 }
740
741 return bs;
742}
743
744/*!
745 \return the error message from the last bake() run, or an empty string if
746 there was no error.
747
748 \note Errors include file read errors, compilation, and translation
749 failures. Not requesting any targets or variants does not count as an error
750 even though the resulting QShader is invalid.
751 */
752QString QShaderBaker::errorMessage() const
753{
754 return d->errorMessage;
755}
756
757QT_END_NAMESPACE
758

source code of qtshadertools/src/shadertools/qshaderbaker.cpp