QShaderBaker Class

Compiles a GLSL/Vulkan shader into SPIR-V, translates into other shading languages, and gathers reflection metadata. More...

Header: #include <QShaderBaker>
Since: Qt 6.6

Public Types

Public Functions

QShaderBaker()
~QShaderBaker()
QShader bake()
QString errorMessage() const
void setBatchableVertexShaderExtraInputLocation(int location)
void setBreakOnShaderTranslationError(bool enable)
void setGeneratedShaderVariants(const QList<QShader::Variant> &v)
void setGeneratedShaders(const QList<QShaderBaker::GeneratedShader> &v)
(since 6.7) void setMultiViewCount(int count)
void setPerTargetCompilation(bool enable)
void setPreamble(const QByteArray &preamble)
void setSourceDevice(QIODevice *device, QShader::Stage stage, const QString &fileName = QString())
void setSourceFileName(const QString &fileName)
void setSourceFileName(const QString &fileName, QShader::Stage stage)
void setSourceString(const QByteArray &sourceString, QShader::Stage stage, const QString &fileName = QString())
void setTessellationMode(QShaderDescription::TessellationMode mode)
void setTessellationOutputVertexCount(int count)

Detailed Description

Warning: QShaderBaker, just like the QRhi family of classes in the Qt Gui module, including QShader and QShaderDescription, offers limited compatibility guarantees. There are no source or binary compatibility guarantees for these classes, meaning the API is only guaranteed to work with the Qt version the application was developed against. Source incompatible changes are however aimed to be kept at a minimum and will only be made in minor releases (6.7, 6.8, and so on). To use this class in an application, link to Qt::ShaderToolsPrivate (if using CMake), and include the headers with the rhi prefix, for example #include <rhi/qshaderbaker.h>.

QShaderBaker takes a graphics (vertex, fragment, etc.) or compute shader, and produces multiple - either source or bytecode - variants of it, together with reflection information. The results are represented by a QShader instance, which also provides simple and fast serialization and deserialization.

Note: Applications and libraries are recommended to avoid using this class directly. Rather, all Qt users are encouraged to rely on offline compilation by invoking the qsb command-line tool at build time via CMake. The qsb tool uses QShaderBaker and writes the serialized version of the generated QShader into a file. The usage of this class should be restricted to cases where run time compilation cannot be avoided, such as when working with user-provided or dynamically generated shader source strings.

The input format is always assumed to be Vulkan-flavored GLSL at the moment. See the GL_KHR_vulkan_glsl specification for an overview, keeping in mind that the Qt Shader Tools module is meant to be used in combination with the QRhi classes from Qt Rendering Hardware Interface module, and therefore a number of concepts and constructs (push constants, storage buffers, subpasses, etc.) are not applicable at the moment. Additional options may be introduced in the future, for example, by enabling HLSL as a source format, once HLSL to SPIR-V compilation is deemed suitable.

The reflection metadata is retrievable from the resulting QShader by calling QShader::description(). This is essential when having to discover what set of vertex inputs and shader resources a shader expects, and what the layouts of those are, as many modern graphics APIs offer no built-in shader reflection capabilities.

Typical Workflow

Let's assume an application has a vertex and fragment shader like the following:

Vertex shader:

 #version 440

 layout(location = 0) in vec4 position;
 layout(location = 1) in vec3 color;
 layout(location = 0) out vec3 v_color;

 layout(std140, binding = 0) uniform buf {
     mat4 mvp;
     float opacity;
 };

 void main()
 {
     v_color = color;
     gl_Position = mvp * position;
 }

Fragment shader:

 #version 440

 layout(location = 0) in vec3 v_color;
 layout(location = 0) out vec4 fragColor;

 layout(std140, binding = 0) uniform buf {
     mat4 mvp;
     float opacity;
 };

 void main()
 {
     fragColor = vec4(v_color * opacity, opacity);
 }

To get QShader instances that can be passed as-is to a QRhiGraphicsPipeline, there are two options: doing the shader pack generation off line, or at run time.

The former involves running the qsb tool:

 qsb --glsl "100 es,120" --hlsl 50 --msl 12 color.vert -o color.vert.qsb
 qsb --glsl "100 es,120" --hlsl 50 --msl 12 color.frag -o color.frag.qsb

The example uses the translation targets as appropriate for QRhi. This means GLSL/ES 100, GLSL 120, HLSL Shader Model 5.0, and Metal Shading Language 1.2.

Note how the command line options correspond to what can be specified via setGeneratedShaders(). Once the resulting files are available, they can be shipped with the application (typically embedded into the executable the the Qt Resource System), and can be loaded and passed to QShader::fromSerialized() at run time.

While not shown here, qsb can do more: it is also able to invoke fxc on Windows or the appropriate XCode tools on macOS to compile the generated HLSL or Metal shader code into bytecode and include the compiled versions in the QShader. After a baked shader pack is written into a file, its contents can be examined by running qsb -d on it. Run qsb with --help for more information.

The alternative approach is to perform the same at run time. This involves creating a QShaderBaker instance, calling setSourceFileName(), and then setting up the translation targets via setGeneratedShaders():

 baker.setGeneratedShaderVariants({ QShader::StandardShader });
 QList<QShaderBaker::GeneratedShader> targets;
 targets.append({ QShader::SpirvShader, QShaderVersion(100) });
 targets.append({ QShader::GlslShader, QShaderVersion(100, QShaderVersion::GlslEs) });
 targets.append({ QShader::SpirvShader, QShaderVersion(120) });
 targets.append({ QShader::HlslShader, QShaderVersion(50) });
 targets.append({ QShader::MslShader, QShaderVersion(12) });
 baker.setGeneratedShaders(targets);
 QShader shaders = baker.bake();
 if (!shaders.isValid())
     qWarning() << baker.errorMessage();

See also QShader.

Member Type Documentation

QShaderBaker::GeneratedShader

Synonym for QPair<QShader::Source, QShaderVersion>.

Member Function Documentation

QShaderBaker::QShaderBaker()

Constructs a new QShaderBaker.

[noexcept] QShaderBaker::~QShaderBaker()

Destructor.

QShader QShaderBaker::bake()

Runs the compilation and translation process.

Returns a QShader instance. To check if the process was successful, call QShader::isValid(). When that indicates false, call errorMessage() to retrieve the log.

This is an expensive operation. When calling this from applications, it can be advisable to do it on a separate thread.

Note: QShaderBaker instances are reusable: after calling bake(), the same instance can be used with different inputs again. However, a QShaderBaker instance should only be used on one single thread during its lifetime.

QString QShaderBaker::errorMessage() const

Returns the error message from the last bake() run, or an empty string if there was no error.

Note: Errors include file read errors, compilation, and translation failures. Not requesting any targets or variants does not count as an error even though the resulting QShader is invalid.

void QShaderBaker::setBatchableVertexShaderExtraInputLocation(int location)

When generating a QShader::BatchableVertexShader variant, location specifies the input location for the inserted vertex input. The value is by default 7 and needs to be overridden only if the vertex shader already uses input location 7.

void QShaderBaker::setBreakOnShaderTranslationError(bool enable)

Controls the behavior when shader translation (from SPIR-V to GLSL/HLSL/MSL) fails. By default this setting is true, which will cause bake() to return with an error if a requested shader cannot be generated. If that is not desired, and the intention is to generate what we can but silently skip the rest, then set enable to false.

Targeting multiple GLSL versions can lead to errors when a feature is not translatable to a given version. For example, attempting to translate a shader using textureSize() to GLSL ES 100 would fail the entire bake() call with the error message "textureSize is not supported in ESSL 100". If it is acceptable to not have a GLSL ES 100 shader in the result, even though it was requested, then setting this flag to false makes bake() to succeed.

void QShaderBaker::setGeneratedShaderVariants(const QList<QShader::Variant> &v)

Specifies which shader variants are generated. Each shader version can have multiple variants in the resulting QShader.

In most cases v contains a single entry, QShader::StandardShader.

Note: when no variants are set, the resulting QShader will be empty and thus invalid.

void QShaderBaker::setGeneratedShaders(const QList<QShaderBaker::GeneratedShader> &v)

Specifies what kind of shaders to compile or translate to. Nothing is generated by default so calling this function before bake() is mandatory

Note: when this function is not called or v is empty or contains only invalid entries, the resulting QShader will be empty and thus invalid.

For example, the minimal possible baking target is SPIR-V, without any additional translations to other languages. To request this, do:

 baker.setGeneratedShaders({ QShader::SpirvShader, QShaderVersion(100) });

Note: QShaderBaker only handles the SPIR-V and human-readable source targets. Further compilation into API-specific intermediate formats, such as QShader::DxbcShader or QShader::MetalLibShader is implemented by the qsb command-line tool, and is not part of the QShaderBaker runtime API.

[since 6.7] void QShaderBaker::setMultiViewCount(int count)

When transpiling shaders using multiview (e.g. a vertex shader using gl_ViewIndex for a renderer relying on GL_OVR_multiview2, VK_KHR_multiview, etc.), for some of the targets it is necessary to declare the number of views in the shader. This is not done in the Vulkan-style GLSL code, and is not relevant for targets such as SPIR-V or HLSL, but is required for OpenGL and GLSL, and so the value has to be provided as additional metadata.

By default the value is 0, which disables injecting the num_views statement. Setting 1 is not useful since that is the default num_views regardless. Therefore count should be >= 2 to make an effect. When set to, for example, 2, the generated GLSL shader will contain a layout(num_views = 2) in; statement.

Setting a count of 2 or greater also injects some preprocessor statements: QSHADER_VIEW_COUNT is set to count, whereas the GL_EXT_multiview extension is enabled automatically. Therefore, setting the appropriate count can be relevant with other types of shaders as well, e.g. when sharing a uniform buffer between the vertex and fragment shader and both shaders have to be able to write something like #if QSHADER_VIEW_COUNT >= 2.

This function was introduced in Qt 6.7.

void QShaderBaker::setPerTargetCompilation(bool enable)

Sets per-target compilation to enable. By default this is disabled, meaning that the Vulkan/GLSL source is compiled to SPIR-V once per variant. (so once by default, twice if it is a vertex shader and the Batchable variant as requested as well). The resulting SPIR-V is then translated to the various target languages (GLSL, HLSL, MSL).

In per-target compilation mode, there is a separate GLSL to SPIR-V compilation step for each target, meaning for each GLSL/HLSL/MSL version requested via setGeneratedShaders(). The input source is the same, but with target-specific preprocessor defines inserted. This is significantly more time consuming, but allows applications to provide a single shader and use #ifdef blocks to differentiate. When this mode is disabled, the only way to achieve the same is to provide multiple versions of the shader file, process each separately, ship {.qsb} files for each, and choose the right file based on run time logic.

The following macros will be automatically defined in this mode. Note that the macros are always tied to shading languages, not graphics APIs.

  • QSHADER_SPIRV - defined when targeting SPIR-V (to be consumed, typically, by Vulkan).
  • QSHADER_SPIRV_VERSION - the targeted SPIR-V version number, such as 100.
  • QSHADER_GLSL - defined when targeting GLSL or GLSL ES (to be consumed, typically, by OpenGL or OpenGL ES)
  • QSHADER_GLSL_VERSION - the targeted GLSL or GLSL ES version number, such as 100, 300, or 330.
  • QSHADER_GLSL_ES - defined only when targeting GLSL ES
  • QSHADER_HLSL - defined when targeting HLSL (to be consumed, typically, by Direct 3D)
  • QSHADER_HLSL_VERSION - the targeted HLSL shader model version, such as 50
  • QSHADER_MSL - defined when targeting the Metal Shading Language (to be consumed, typically, by Metal)
  • QSHADER_MSL_VERSION - the targeted MSL version, such as 12 or 20.

This allows writing shader code like the following.

 #if QSHADER_HLSL || QSHADER_MSL
 vec2 uv = vec2(uv_coord.x, 1.0 - uv_coord.y);
 #else
 vec2 uv = uv_coord;
 #endif

Note: Version numbers follow the GLSL-inspired QShaderVersion syntax and thus are a single integer always.

Note: There is only one QShaderDescription per QShader, no matter how many individual targets there are. Therefore members of uniform blocks, vertex inputs, etc. must not be made conditional using the macros described above.

Warning: Be aware of the differences between the concepts of graphics APIs and shading languages. QShaderBaker and the related tools work strictly with the concept of shading languages, ignoring how the results are consumed afterwards. Therefore, if the higher layers in the Qt graphics stack one day start using SPIR-V also for an API other than Vulkan, the assumption that QSHADER_SPIRV implies Vulkan will no longer hold.

void QShaderBaker::setPreamble(const QByteArray &preamble)

Specifies a custom preamble that is processed before the normal shader code.

This is more than just prepending to the source string: the validity of the GLSL version directive, which is required to be placed before everything else, is not affected. Line numbers in the reported error messages also remain unchanged, ignoring the contents given in the preamble.

One use case for preambles is to transparently insert dynamically generated #define statements.

void QShaderBaker::setSourceDevice(QIODevice *device, QShader::Stage stage, const QString &fileName = QString())

Sets the source device. This allows using any QIODevice instead of just files. stage specifies the shader stage, while the optional fileName contains a filename that is used in the error messages.

void QShaderBaker::setSourceFileName(const QString &fileName)

Sets the name of the shader source file to fileName. This is the file that will be read when calling bake(). The shader stage is deduced automatically from the file extension. When this is not desired or not possible, use the overload with the stage argument instead.

The supported file extensions are:

  • .vert - vertex shader
  • .frag - fragment (pixel) shader
  • .tesc - tessellation control (hull) shader
  • .tese - tessellation evaluation (domain) shader
  • .geom - geometry shader
  • .comp - compute shader

void QShaderBaker::setSourceFileName(const QString &fileName, QShader::Stage stage)

Sets the name of the shader source file to fileName. This is the file that will be read when calling bake(). The shader stage is specified by stage.

void QShaderBaker::setSourceString(const QByteArray &sourceString, QShader::Stage stage, const QString &fileName = QString())

Sets the input shader sourceString. stage specified the shader stage, while the optional fileName contains a filename that is used in the error messages.

void QShaderBaker::setTessellationMode(QShaderDescription::TessellationMode mode)

When generating MSL shader code for a tessellation control shader, the tessellation mode (triangles or quads) must be known upfront. In GLSL this is declared in the tessellation evaluation shader typically, but for Metal it must be known also when generating the compute shader from the tessellation control shader.

When not set, the default is triangles.

void QShaderBaker::setTessellationOutputVertexCount(int count)

When generating MSL shader code for a tessellation evaluation shader, the output vertex count of the tessellation control shader must be known upfront. in GLSL this would be declared in the tessellation control shader typically, but for Metal it must be known also when generating the vertex shader from the teselation evaluation shader.

When not set, the default value is 3.