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 | |
11 | QT_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 | |
123 | struct 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 | |
144 | bool 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 | */ |
159 | QShaderBaker::QShaderBaker() |
160 | : d(new QShaderBakerPrivate) |
161 | { |
162 | } |
163 | |
164 | /*! |
165 | Destructor. |
166 | */ |
167 | QShaderBaker::~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 | */ |
188 | void 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 | */ |
217 | void 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 | */ |
228 | void 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 | */ |
238 | void 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 | */ |
270 | void 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 | */ |
284 | void 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 | */ |
301 | void 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 | */ |
312 | void QShaderBaker::(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 | */ |
391 | void 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 | */ |
410 | void 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 | */ |
424 | void 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 | */ |
438 | void QShaderBaker::setTessellationOutputVertexCount(int count) |
439 | { |
440 | d->tessInfo.infoForTese.vertexCount = count; |
441 | } |
442 | |
443 | void QShaderBaker::setSpirvOptions(SpirvOptions options) |
444 | { |
445 | d->spirvOptions = options; |
446 | } |
447 | |
448 | inline size_t qHash(const QShaderBaker::GeneratedShader &k, size_t seed = 0) |
449 | { |
450 | return qHash(e: k.first, seed) ^ k.second.version(); |
451 | } |
452 | |
453 | QPair<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 | |
478 | QByteArray 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 | */ |
524 | QShader 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 | */ |
752 | QString QShaderBaker::errorMessage() const |
753 | { |
754 | return d->errorMessage; |
755 | } |
756 | |
757 | QT_END_NAMESPACE |
758 | |