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

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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