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 QSpirvShader::MultiViewInfo multiViewInfo;
140 QShaderBaker::SpirvOptions spirvOptions;
141 QShaderBaker::GlslOptions glslOptions;
142 QSpirvCompiler compiler;
143 QString errorMessage;
144};
145
146bool QShaderBakerPrivate::readFile(const QString &fn)
147{
148 QFile f(fn);
149 if (!f.open(flags: QIODevice::ReadOnly | QIODevice::Text)) {
150 qWarning(msg: "QShaderBaker: Failed to open %s", qPrintable(fn));
151 return false;
152 }
153 source = f.readAll();
154
155 // Remove UTF-8 BOM, if present. glslang fails otherwise, since the BOM is
156 // removed in glslangValidator (the command-line frontend), not in the
157 // compiler itself. Replicate that here.
158 if (source.size() >= 3) {
159 const unsigned char *p = reinterpret_cast<const unsigned char *>(source.constData());
160 if (p[0] == 0xEF && p[1] == 0xBB && p[2] == 0xBF)
161 source.remove(index: 0, len: 3);
162 }
163
164 sourceFileName = fn;
165 return true;
166}
167
168/*!
169 Constructs a new QShaderBaker.
170 */
171QShaderBaker::QShaderBaker()
172 : d(new QShaderBakerPrivate)
173{
174}
175
176/*!
177 Destructor.
178 */
179QShaderBaker::~QShaderBaker()
180{
181 delete d;
182}
183
184/*!
185 Sets the name of the shader source file to \a fileName. This is the file
186 that will be read when calling bake(). The shader stage is deduced
187 automatically from the file extension. When this is not desired or not
188 possible, use the overload with the stage argument instead.
189
190 The supported file extensions are:
191 \list
192 \li \c{.vert} - vertex shader
193 \li \c{.frag} - fragment (pixel) shader
194 \li \c{.tesc} - tessellation control (hull) shader
195 \li \c{.tese} - tessellation evaluation (domain) shader
196 \li \c{.geom} - geometry shader
197 \li \c{.comp} - compute shader
198 \endlist
199
200 \warning \a fileName is expected to contain trusted content. Application
201 developers are advised to carefully consider the potential implications
202 before passing in user-provided source files that are not part of the
203 application.
204 */
205void QShaderBaker::setSourceFileName(const QString &fileName)
206{
207 if (!d->readFile(fn: fileName))
208 return;
209
210 const QString suffix = QFileInfo(fileName).suffix();
211 if (suffix == QStringLiteral("vert")) {
212 d->stage = QShader::VertexStage;
213 } else if (suffix == QStringLiteral("frag")) {
214 d->stage = QShader::FragmentStage;
215 } else if (suffix == QStringLiteral("tesc")) {
216 d->stage = QShader::TessellationControlStage;
217 } else if (suffix == QStringLiteral("tese")) {
218 d->stage = QShader::TessellationEvaluationStage;
219 } else if (suffix == QStringLiteral("geom")) {
220 d->stage = QShader::GeometryStage;
221 } else if (suffix == QStringLiteral("comp")) {
222 d->stage = QShader::ComputeStage;
223 } else {
224 qWarning(msg: "QShaderBaker: Unknown shader stage, defaulting to vertex");
225 d->stage = QShader::VertexStage;
226 }
227}
228
229/*!
230 Sets the name of the shader source file to \a fileName. This is the file
231 that will be read when calling bake(). The shader stage is specified by \a
232 stage.
233
234 \warning \a fileName is expected to contain trusted content. Application
235 developers are advised to carefully consider the potential implications
236 before passing in user-provided source files that are not part of the
237 application.
238 */
239void QShaderBaker::setSourceFileName(const QString &fileName, QShader::Stage stage)
240{
241 if (d->readFile(fn: fileName))
242 d->stage = stage;
243}
244
245/*!
246 Sets the source \a device. This allows using any QIODevice instead of just
247 files. \a stage specifies the shader stage, while the optional \a fileName
248 contains a filename that is used in the error messages.
249
250 \warning \a device is expected to contain trusted content. Application
251 developers are advised to carefully consider the potential implications
252 before passing in user-provided data from sources that are not under the
253 application's control.
254 */
255void QShaderBaker::setSourceDevice(QIODevice *device, QShader::Stage stage, const QString &fileName)
256{
257 setSourceString(sourceString: device->readAll(), stage, fileName);
258}
259
260/*!
261 Sets the input shader \a sourceString. \a stage specified the shader stage,
262 while the optional \a fileName contains a filename that is used in the
263 error messages.
264
265 \warning \a sourceString is expected to contain trusted content. Application
266 developers are advised to carefully consider the potential implications
267 before passing in user-provided data from sources that are not under the
268 application's control.
269 */
270void QShaderBaker::setSourceString(const QByteArray &sourceString, QShader::Stage stage, const QString &fileName)
271{
272 d->sourceFileName = fileName; // for error messages, include handling, etc.
273 d->source = sourceString;
274 d->stage = stage;
275}
276
277/*!
278 \typedef QShaderBaker::GeneratedShader
279
280 Synonym for QPair<QShader::Source, QShaderVersion>.
281*/
282
283/*!
284 Specifies what kind of shaders to compile or translate to. Nothing is
285 generated by default so calling this function before bake() is mandatory
286
287 \note when this function is not called or \a v is empty or contains only invalid
288 entries, the resulting QShader will be empty and thus invalid.
289
290 For example, the minimal possible baking target is SPIR-V, without any
291 additional translations to other languages. To request this, do:
292
293 \badcode
294 baker.setGeneratedShaders({ QShader::SpirvShader, QShaderVersion(100) });
295 \endcode
296
297 \note QShaderBaker only handles the SPIR-V and human-readable source
298 targets. Further compilation into API-specific intermediate formats, such
299 as QShader::DxbcShader or QShader::MetalLibShader is implemented by the
300 \c qsb command-line tool, and is not part of the QShaderBaker runtime API.
301 */
302void QShaderBaker::setGeneratedShaders(const QList<GeneratedShader> &v)
303{
304 d->reqVersions = v;
305}
306
307/*!
308 Specifies which shader variants are generated. Each shader version can have
309 multiple variants in the resulting QShader.
310
311 In most cases \a v contains a single entry, QShader::StandardShader.
312
313 \note when no variants are set, the resulting QShader will be empty and
314 thus invalid.
315 */
316void QShaderBaker::setGeneratedShaderVariants(const QList<QShader::Variant> &v)
317{
318 d->variants = v;
319}
320
321/*!
322 Specifies a custom \a preamble that is processed before the normal shader
323 code.
324
325 This is more than just prepending to the source string: the validity of the
326 GLSL version directive, which is required to be placed before everything
327 else, is not affected. Line numbers in the reported error messages also
328 remain unchanged, ignoring the contents given in the \a preamble.
329
330 One use case for preambles is to transparently insert dynamically generated
331 \c{#define} statements.
332 */
333void QShaderBaker::setPreamble(const QByteArray &preamble)
334{
335 d->preamble = preamble;
336}
337
338/*!
339 When generating a QShader::BatchableVertexShader variant, \a location
340 specifies the input location for the inserted vertex input. The value is by
341 default 7 and needs to be overridden only if the vertex shader already uses
342 input location 7.
343 */
344void QShaderBaker::setBatchableVertexShaderExtraInputLocation(int location)
345{
346 d->batchLoc = location;
347}
348
349/*!
350 Sets per-target compilation to \a enable. By default this is disabled,
351 meaning that the Vulkan/GLSL source is compiled to SPIR-V once per variant.
352 (so once by default, twice if it is a vertex shader and the Batchable
353 variant as requested as well). The resulting SPIR-V is then translated to
354 the various target languages (GLSL, HLSL, MSL).
355
356 In per-target compilation mode, there is a separate GLSL to SPIR-V
357 compilation step for each target, meaning for each GLSL/HLSL/MSL version
358 requested via setGeneratedShaders(). The input source is the same, but with
359 target-specific preprocessor defines inserted. This is significantly more
360 time consuming, but allows applications to provide a single shader and use
361 \c{#ifdef} blocks to differentiate. When this mode is disabled, the only
362 way to achieve the same is to provide multiple versions of the shader file,
363 process each separately, ship {.qsb} files for each, and choose the right
364 file based on run time logic.
365
366 The following macros will be automatically defined in this mode. Note that
367 the macros are always tied to shading languages, not graphics APIs.
368
369 \list
370
371 \li \c{QSHADER_SPIRV} - defined when targeting SPIR-V (to be consumed,
372 typically, by Vulkan).
373
374 \li \c{QSHADER_SPIRV_VERSION} - the targeted SPIR-V version number, such as
375 \c 100.
376
377 \li \c{QSHADER_GLSL} - defined when targeting GLSL or GLSL ES (to be
378 consumed, typically, by OpenGL or OpenGL ES)
379
380 \li \c{QSHADER_GLSL_VERSION} - the targeted GLSL or GLSL ES version number,
381 such as \c 100, \c 300, or \c 330.
382
383 \li \c{QSHADER_GLSL_ES} - defined only when targeting GLSL ES
384
385 \li \c{QSHADER_HLSL} - defined when targeting HLSL (to be consumed,
386 typically, by Direct 3D)
387
388 \li \c{QSHADER_HLSL_VERSION} - the targeted HLSL shader model version, such
389 as \c 50
390
391 \li \c{QSHADER_MSL} - defined when targeting the Metal Shading Language (to
392 be consumed, typically, by Metal)
393
394 \li \c{QSHADER_MSL_VERSION} - the targeted MSL version, such as \c 12 or
395 \c 20.
396
397 \endlist
398
399 This allows writing shader code like the following.
400
401 \badcode
402 #if QSHADER_HLSL || QSHADER_MSL
403 vec2 uv = vec2(uv_coord.x, 1.0 - uv_coord.y);
404 #else
405 vec2 uv = uv_coord;
406 #endif
407 \endcode
408
409 \note Version numbers follow the GLSL-inspired QShaderVersion syntax and
410 thus are a single integer always.
411
412 \note There is only one QShaderDescription per QShader, no matter how many
413 individual targets there are. Therefore members of uniform blocks, vertex
414 inputs, etc. must not be made conditional using the macros described above.
415
416 \warning Be aware of the differences between the concepts of graphics APIs
417 and shading languages. QShaderBaker and the related tools work strictly
418 with the concept of shading languages, ignoring how the results are
419 consumed afterwards. Therefore, if the higher layers in the Qt graphics
420 stack one day start using SPIR-V also for an API other than Vulkan, the
421 assumption that QSHADER_SPIRV implies Vulkan will no longer hold.
422 */
423void QShaderBaker::setPerTargetCompilation(bool enable)
424{
425 d->perTargetEnabled = enable;
426}
427
428/*!
429 Controls the behavior when shader translation (from SPIR-V to
430 GLSL/HLSL/MSL) fails. By default this setting is true, which will cause
431 bake() to return with an error if a requested shader cannot be generated.
432 If that is not desired, and the intention is to generate what we can but
433 silently skip the rest, then set \a enable to false.
434
435 Targeting multiple GLSL versions can lead to errors when a feature is not
436 translatable to a given version. For example, attempting to translate a
437 shader using textureSize() to GLSL ES 100 would fail the entire bake() call
438 with the error message "textureSize is not supported in ESSL 100". If it is
439 acceptable to not have a GLSL ES 100 shader in the result, even though it
440 was requested, then setting this flag to false makes bake() to succeed.
441 */
442void QShaderBaker::setBreakOnShaderTranslationError(bool enable)
443{
444 d->breakOnShaderTranslationError = enable;
445}
446
447/*!
448 When generating MSL shader code for a tessellation control shader, the
449 tessellation \a mode (triangles or quads) must be known upfront. In GLSL
450 this is declared in the tessellation evaluation shader typically, but for
451 Metal it must be known also when generating the compute shader from the
452 tessellation control shader.
453
454 When not set, the default is triangles.
455 */
456void QShaderBaker::setTessellationMode(QShaderDescription::TessellationMode mode)
457{
458 d->tessInfo.infoForTesc.mode = mode;
459}
460
461/*!
462 When generating MSL shader code for a tessellation evaluation shader, the
463 output vertex \a count of the tessellation control shader must be known
464 upfront. in GLSL this would be declared in the tessellation control shader
465 typically, but for Metal it must be known also when generating the vertex
466 shader from the teselation evaluation shader.
467
468 When not set, the default value is 3.
469 */
470void QShaderBaker::setTessellationOutputVertexCount(int count)
471{
472 d->tessInfo.infoForTese.vertexCount = count;
473}
474
475/*!
476 When transpiling shaders using multiview (e.g. a vertex shader using
477 gl_ViewIndex for a renderer relying on GL_OVR_multiview2, VK_KHR_multiview,
478 etc.), for some of the targets it is necessary to declare the number of
479 views in the shader. This is not done in the Vulkan-style GLSL code, and is
480 not relevant for targets such as SPIR-V or HLSL, but is required for OpenGL
481 and GLSL, and so the value has to be provided as additional metadata.
482
483 By default the value is 0, which disables injecting the \c{num_views}
484 statement. Setting 1 is not useful since that is the default \c{num_views}
485 regardless. Therefore \a count should be >= 2 to make an effect. When set
486 to, for example, 2, the generated GLSL shader will contain a
487 \c{layout(num_views = 2) in;} statement.
488
489 Setting a \a count of 2 or greater also injects some preprocessor
490 statements:
491 \c{QSHADER_VIEW_COUNT} is set to \a count, whereas the
492 \c GL_EXT_multiview extension is enabled automatically. Therefore, setting
493 the appropriate
494 \a count can be relevant with other types of shaders as well, e.g. when
495 sharing a uniform buffer between the vertex and fragment shader and both
496 shaders have to be able to write something like
497 \c{#if QSHADER_VIEW_COUNT >= 2}.
498
499 \since 6.7
500 */
501void QShaderBaker::setMultiViewCount(int count)
502{
503 d->multiViewInfo.viewCount = count >= 2 ? count : 0;
504}
505
506/*!
507 \enum QShaderBaker::SpirvOption
508 \value GenerateFullDebugInfo Generate and store additional debug information in the SPIR-V binary.
509 \value StripDebugAndVarInfo Strip all debug and variable name information from the SPIR-V binary.
510 */
511
512/*!
513 Sets additional \a options for the generated SPIR-V binary.
514 By default no flags are set.
515 */
516void QShaderBaker::setSpirvOptions(SpirvOptions options)
517{
518 d->spirvOptions = options;
519}
520
521/*!
522 \enum QShaderBaker::GlslOption
523 \value GlslEsFragDefaultFloatPrecisionMedium Emit \c{precision mediump float;} in fragment shaders for GLSL ES.
524 */
525
526/*!
527 Sets additional \a options for the generated GLSL and GLSL ES sources.
528 By default no flags are set.
529 \since 6.9
530 */
531void QShaderBaker::setGlslOptions(GlslOptions options)
532{
533 d->glslOptions = options;
534}
535
536inline size_t qHash(const QShaderBaker::GeneratedShader &k, size_t seed = 0)
537{
538 return qHash(e: k.first, seed) ^ k.second.version();
539}
540
541QPair<QByteArray, QByteArray> QShaderBakerPrivate::compile()
542{
543 QSpirvCompiler::Flags flags;
544 if (spirvOptions.testFlag(flag: QShaderBaker::SpirvOption::GenerateFullDebugInfo))
545 flags |= QSpirvCompiler::FullDebugInfo;
546
547 compiler.setFlags(flags);
548 const QByteArray spirvBin = compiler.compileToSpirv();
549 if (spirvBin.isEmpty()) {
550 errorMessage = compiler.errorMessage();
551 return {};
552 }
553 QByteArray batchableSpirvBin;
554 if (stage == QShader::VertexStage && variants.contains(t: QShader::BatchableVertexShader)) {
555 compiler.setFlags(flags | QSpirvCompiler::RewriteToMakeBatchableForSG);
556 compiler.setSGBatchingVertexInputLocation(batchLoc);
557 batchableSpirvBin = compiler.compileToSpirv();
558 if (batchableSpirvBin.isEmpty()) {
559 errorMessage = compiler.errorMessage();
560 return {};
561 }
562 }
563 return { spirvBin, batchableSpirvBin };
564}
565
566QByteArray QShaderBakerPrivate::perTargetDefines(const QShaderBaker::GeneratedShader &key)
567{
568 QByteArray preamble;
569 switch (key.first) {
570 case QShader::SpirvShader:
571 preamble += QByteArrayLiteral("\n#define QSHADER_SPIRV 1\n#define QSHADER_SPIRV_VERSION ");
572 preamble += QByteArray::number(key.second.version());
573 preamble += QByteArrayLiteral("\n");
574 break;
575 case QShader::GlslShader:
576 preamble += QByteArrayLiteral("\n#define QSHADER_GLSL 1\n#define QSHADER_GLSL_VERSION ");
577 preamble += QByteArray::number(key.second.version());
578 if (key.second.flags().testFlag(flag: QShaderVersion::GlslEs))
579 preamble += QByteArrayLiteral("\n#define QSHADER_GLSL_ES 1");
580 preamble += QByteArrayLiteral("\n");
581 break;
582 case QShader::HlslShader:
583 preamble += QByteArrayLiteral("\n#define QSHADER_HLSL 1\n#define QSHADER_HLSL_VERSION ");
584 preamble += QByteArray::number(key.second.version());
585 preamble += QByteArrayLiteral("\n");
586 break;
587 case QShader::MslShader:
588 preamble += QByteArrayLiteral("\n#define QSHADER_MSL 1\n#define QSHADER_MSL_VERSION ");
589 preamble += QByteArray::number(key.second.version());
590 preamble += QByteArrayLiteral("\n");
591 break;
592 default:
593 Q_UNREACHABLE();
594 }
595 return preamble;
596}
597
598/*!
599 Runs the compilation and translation process.
600
601 \return a QShader instance. To check if the process was successful,
602 call QShader::isValid(). When that indicates \c false, call
603 errorMessage() to retrieve the log.
604
605 This is an expensive operation. When calling this from applications, it can
606 be advisable to do it on a separate thread.
607
608 \note QShaderBaker instances are reusable: after calling bake(), the same
609 instance can be used with different inputs again. However, a QShaderBaker
610 instance should only be used on one single thread during its lifetime.
611 */
612QShader QShaderBaker::bake()
613{
614 d->errorMessage.clear();
615
616 if (d->source.isEmpty()) {
617 d->errorMessage = QLatin1String("QShaderBaker: No source specified");
618 return QShader();
619 }
620
621 d->compiler.setSourceString(sourceString: d->source, stage: d->stage, fileName: d->sourceFileName);
622
623 // Normally one entry, for QShader::SpirvShader only. However, in
624 // compile-per-target mode there is a separate SPIR-V binary generated for
625 // each target (so for each GLSL/HLSL/MSL version requested).
626 QHash<GeneratedShader, QByteArray> spirv;
627 QHash<GeneratedShader, QByteArray> batchableSpirv;
628 const auto compileSpirvAndBatchable = [this, &spirv, &batchableSpirv](const GeneratedShader &key) {
629 const QPair<QByteArray, QByteArray> bin = d->compile();
630 if (bin.first.isEmpty())
631 return false;
632 spirv.insert(key, value: bin.first);
633 if (!bin.second.isEmpty())
634 batchableSpirv.insert(key, value: bin.second);
635 return true;
636 };
637
638 QByteArray preamble = d->preamble;
639 if (d->multiViewInfo.viewCount >= 2) {
640 if (d->stage == QShader::VertexStage)
641 preamble += QByteArrayLiteral("\n#extension GL_EXT_multiview : require\n");
642 preamble += QByteArrayLiteral("\n#define QSHADER_VIEW_COUNT ");
643 preamble += QByteArray::number(d->multiViewInfo.viewCount);
644 preamble += QByteArrayLiteral("\n");
645 }
646
647 if (!d->perTargetEnabled) {
648 d->compiler.setPreamble(preamble);
649 if (!compileSpirvAndBatchable({ QShader::SpirvShader, {} }))
650 return QShader();
651 } else {
652 // per-target compilation. the value here comes from the varying
653 // preamble (and so preprocessor defines)
654 for (GeneratedShader req: d->reqVersions) {
655 d->compiler.setPreamble(preamble + d->perTargetDefines(key: req));
656 if (!compileSpirvAndBatchable(req))
657 return QShader();
658 }
659 }
660
661 // Now spirv, and, if in use, batchableSpirv, contain at least one,
662 // optionally more SPIR-V binaries.
663 Q_ASSERT(!spirv.isEmpty() && (d->perTargetEnabled || spirv.size() == 1));
664
665 QShader bs;
666 bs.setStage(d->stage);
667
668 QSpirvShader spirvShader;
669 QSpirvShader batchableSpirvShader;
670 // The QShaderDescription can be different for variants (we just have a
671 // hardcoded rule to pick one), but cannot differ for targets (in
672 // per-target mode, hence we can just pick the first SPIR-V binary and
673 // generate the reflection data based on that)
674 spirvShader.setSpirvBinary(spirv: spirv.constKeyValueBegin()->second, stage: d->stage);
675 if (batchableSpirv.isEmpty()) {
676 bs.setDescription(spirvShader.shaderDescription());
677 } else {
678 batchableSpirvShader.setSpirvBinary(spirv: batchableSpirv.constKeyValueBegin()->second, stage: d->stage);
679 // prefer the batchable's reflection info with _qt_order and such present
680 bs.setDescription(batchableSpirvShader.shaderDescription());
681 }
682
683 for (const GeneratedShader &req: d->reqVersions) {
684 for (const QShader::Variant &v : d->variants) {
685 if (d->stage != QShader::VertexStage) {
686 if (v == QShader::BatchableVertexShader
687 || v == QShader::UInt32IndexedVertexAsComputeShader
688 || v == QShader::UInt16IndexedVertexAsComputeShader
689 || v == QShader::NonIndexedVertexAsComputeShader)
690 {
691 continue;
692 }
693 }
694 if (req.first != QShader::MslShader && req.first != QShader::MetalLibShader) {
695 if (v == QShader::UInt32IndexedVertexAsComputeShader
696 || v == QShader::UInt16IndexedVertexAsComputeShader
697 || v == QShader::NonIndexedVertexAsComputeShader)
698 {
699 continue;
700 }
701 }
702
703 QSpirvShader *currentSpirvShader = nullptr;
704 if (d->perTargetEnabled) {
705 // This is expensive too, in addition to the multiple
706 // compilation rounds, but opting in to per-target mode is a
707 // careful, conscious choice (hopefully), so it's fine.
708 if (v == QShader::BatchableVertexShader)
709 batchableSpirvShader.setSpirvBinary(spirv: batchableSpirv[req], stage: d->stage);
710 else
711 spirvShader.setSpirvBinary(spirv: spirv[req], stage: d->stage);
712 }
713 if (v == QShader::BatchableVertexShader)
714 currentSpirvShader = &batchableSpirvShader;
715 else
716 currentSpirvShader = &spirvShader;
717 Q_ASSERT(currentSpirvShader);
718 Q_ASSERT(!currentSpirvShader->spirvBinary().isEmpty());
719 const QShaderKey key(req.first, req.second, v);
720 QShaderCode shader;
721 shader.setEntryPoint(QByteArrayLiteral("main"));
722 switch (req.first) {
723 case QShader::SpirvShader:
724 if (d->spirvOptions.testFlag(flag: QShaderBaker::SpirvOption::StripDebugAndVarInfo)) {
725 QString errorMsg;
726 const QByteArray strippedSpirv = currentSpirvShader->remappedSpirvBinary(flags: QSpirvShader::RemapFlag::StripOnly, errorMessage: &errorMsg);
727 if (strippedSpirv.isEmpty()) {
728 d->errorMessage = errorMsg;
729 return QShader();
730 }
731 shader.setShader(strippedSpirv);
732 } else {
733 shader.setShader(currentSpirvShader->spirvBinary());
734 }
735 break;
736 case QShader::GlslShader:
737 {
738 QSpirvShader::GlslFlags flags;
739 if (req.second.flags().testFlag(flag: QShaderVersion::GlslEs)) {
740 flags |= QSpirvShader::GlslFlag::GlslEs;
741 if (d->glslOptions.testFlag(flag: GlslOption::GlslEsFragDefaultFloatPrecisionMedium))
742 flags |= QSpirvShader::GlslFlag::EsFragDefaultFloatPrecisionMedium;
743 }
744 QVector<QSpirvShader::SeparateToCombinedImageSamplerMapping> separateToCombinedImageSamplerMappings;
745 shader.setShader(currentSpirvShader->translateToGLSL(version: req.second.version(),
746 flags,
747 stage: d->stage,
748 multiViewInfo: d->multiViewInfo,
749 separateToCombinedImageSamplerMappings: &separateToCombinedImageSamplerMappings));
750 if (shader.shader().isEmpty()) {
751 if (d->breakOnShaderTranslationError) {
752 d->errorMessage = currentSpirvShader->translationErrorMessage();
753 return QShader();
754 } else {
755 d->errorMessage += QLatin1String(" ") + currentSpirvShader->translationErrorMessage();
756 continue;
757 }
758 }
759 if (!separateToCombinedImageSamplerMappings.isEmpty()) {
760 const QShaderDescription desc = bs.description();
761 QVector<QShaderDescription::InOutVariable> separateImages = desc.separateImages();
762 QVector<QShaderDescription::InOutVariable> separateSamplers = desc.separateSamplers();
763 QShader::SeparateToCombinedImageSamplerMappingList result;
764 for (const QSpirvShader::SeparateToCombinedImageSamplerMapping &mapping : separateToCombinedImageSamplerMappings) {
765 int textureBinding = -1;
766 int samplerBinding = -1;
767 for (int i = 0, count = separateImages.size(); i < count; ++i) {
768 if (separateImages[i].name == mapping.textureName) {
769 textureBinding = separateImages[i].binding;
770 break;
771 }
772 }
773 for (int i = 0, count = separateSamplers.size(); i < count; ++i) {
774 if (separateSamplers[i].name == mapping.samplerName) {
775 samplerBinding = separateSamplers[i].binding;
776 break;
777 }
778 }
779 result.append(t: { .combinedSamplerName: mapping.combinedSamplerName, .textureBinding: textureBinding, .samplerBinding: samplerBinding });
780 }
781 bs.setSeparateToCombinedImageSamplerMappingList(key, list: result);
782 }
783 }
784 break;
785 case QShader::HlslShader:
786 {
787 QShader::NativeResourceBindingMap nativeBindings;
788 shader.setShader(currentSpirvShader->translateToHLSL(version: req.second.version(), nativeBindings: &nativeBindings));
789 if (shader.shader().isEmpty()) {
790 if (d->breakOnShaderTranslationError) {
791 d->errorMessage = currentSpirvShader->translationErrorMessage();
792 return QShader();
793 } else {
794 d->errorMessage += QLatin1String(" ") + currentSpirvShader->translationErrorMessage();
795 continue;
796 }
797 }
798 bs.setResourceBindingMap(key, map: nativeBindings);
799 }
800 break;
801 case QShader::MslShader:
802 {
803 QShader::NativeResourceBindingMap nativeBindings;
804 QShader::NativeShaderInfo shaderInfo;
805 QSpirvShader::MslFlags flags;
806 if (d->stage == QShader::VertexStage) {
807 switch (v) {
808 case QShader::UInt16IndexedVertexAsComputeShader:
809 flags |= QSpirvShader::MslFlag::VertexAsCompute;
810 flags |= QSpirvShader::MslFlag::WithUInt16Index;
811 break;
812 case QShader::UInt32IndexedVertexAsComputeShader:
813 flags |= QSpirvShader::MslFlag::VertexAsCompute;
814 flags |= QSpirvShader::MslFlag::WithUInt32Index;
815 break;
816 case QShader::NonIndexedVertexAsComputeShader:
817 flags |= QSpirvShader::MslFlag::VertexAsCompute;
818 break;
819 default:
820 break;
821 }
822 }
823 shader.setShader(currentSpirvShader->translateToMSL(version: req.second.version(),
824 flags,
825 stage: d->stage,
826 nativeBindings: &nativeBindings,
827 shaderInfo: &shaderInfo,
828 multiViewInfo: d->multiViewInfo,
829 tessInfo: d->tessInfo));
830 if (shader.shader().isEmpty()) {
831 if (d->breakOnShaderTranslationError) {
832 d->errorMessage = currentSpirvShader->translationErrorMessage();
833 return QShader();
834 } else {
835 d->errorMessage += QLatin1String(" ") + currentSpirvShader->translationErrorMessage();
836 continue;
837 }
838 }
839 shader.setEntryPoint(QByteArrayLiteral("main0"));
840 bs.setResourceBindingMap(key, map: nativeBindings);
841 bs.setNativeShaderInfo(key, info: shaderInfo);
842 }
843 break;
844 default:
845 Q_UNREACHABLE();
846 }
847 bs.setShader(key, shader);
848 }
849 }
850
851 return bs;
852}
853
854/*!
855 \return the error message from the last bake() run, or an empty string if
856 there was no error.
857
858 \note Errors include file read errors, compilation, and translation
859 failures. Not requesting any targets or variants does not count as an error
860 even though the resulting QShader is invalid.
861 */
862QString QShaderBaker::errorMessage() const
863{
864 return d->errorMessage;
865}
866
867QT_END_NAMESPACE
868

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