Qt着色工具构建系统集成

编译着色器并将其添加到Qt资源中

简介

Qt着色工具模块提供了一个CMake宏文件,为应用提供了在他们的CMakeLists.txt中使用的有用函数。

当使用qt6_add_shaders函数时,构建系统会自动调用qsb工具,并将生成的.qsb文件隐式地添加到资源系统中。

第一个示例

让我们看一个简单的示例。假设我们有一个想要通过ShaderEffect提供自己的颤动效果的Qt Quick应用。片段着色器在wobble.frag中实现。《ShaderEffect》项的`fragmentShader`属性引用了`wobble.frag.qsb`。我们如何确保在构建时生成此.qsb文件?

...
project(exampleapp LANGUAGES CXX)
...
find_package(Qt6 COMPONENTS ShaderTools)
...
qt6_add_executable(exampleapp
    main.cpp
)
...
qt6_add_resources(exampleapp "exampleapp"
    PREFIX
        "/"
    FILES
        "main.qml"
)

qt6_add_shaders(exampleapp "exampleapp_shaders"
    PREFIX
        "/"
    FILES
        "wobble.frag"
)

以上足以使应用在运行时访问`:/wobble.frag.qsb`。原始的Vulkan样式的GLSL源代码(wobble.frag)不包括在应用的可执行文件中,并且不需要发货。如果有着色器代码错误,glslang编译器的消息会在构建时打印出来,并且构建失败。当更改着色器源文件时,更改将在下一次构建中自动获取,就像它们对C++和其他源文件所做的那样。

关键是qt6_add_shaders函数,它类似于qt6_add_resources。在不指定进一步参数的情况下,该函数会将qsb运行在默认合理的参数上,这些参数适用于针对Vulkan、Metal、Direct 3D和OpenGL或OpenGL ES的目标的片段着色器。

注意:请注意,需要包含%。它对于ShaderTools的应用非常重要,否则qt6_add_shaders将不可用。

注意:作为qt6_add_shaders函数的第一个参数传递的目标必须在调用此函数之前存在。

注意:支持多个qt6_add_shaders调用。在复杂的应用中,不同的一组着色器可能需要不同的设置。在上述示例中,项目名称(`exampleapp_shaders`)对于每个调用必须是唯一的。

配置

默认情况下,qt6_add_shaders以如下方式调用qsb:

qsb --glsl "100 es,120,150" --hlsl 50 --msl 12 -o <output>.qsb <input>

这意味着生成的包将包含SPIR-V(用于Vulkan 1.0)、GLSL ES 100(用于OpenGL ES 2.0及更高版本)、GLSL 120(用于非核心轮廓OpenGL上下文)、GLSL 150(用于核心轮廓OpenGL上下文)、Shader Model 5.0的HLSL源代码(用于Direct3D 11.1)以及Metal着色语言1.2源代码(用于Metal)。

这是Qt Quick的一个很好的默认值集合,可以创建在各种系统上高度可移植的应用程序。然而,这些默认值并不总是合适的。如果着色器使用了在这些目标中没有等效功能或结构,那么这个流程以及构建将会失败。如果是这种情况,需要调整目标,这也意味着应用程序的最小系统要求将会隐式调整。例如,考虑一下只适用于OpenGL ES 3.0及以上版本(意味着GLSL ES 300或更高版本)的 textureLod GLSL函数。当请求使用 300 es 而不是 100 es 时,构建将会成功,但生成的应用程序现在将需要OpenGL ES 3.0或更高版本,并且不会与基于OpenGL ES 2.0的系统兼容。

着色器类型

着色器的类型是从文件扩展名推断出来的。因此,扩展名必须是以下之一

  • .vert - 用于顶点着色器
  • .tesc - 用于扰动控制着色器
  • .tese - 用于扰动评估着色器
  • .frag - 用于片段(像素)着色器
  • .comp - 用于计算着色器

注意: 扰动控制和评估着色器当前不支持Direct 3D(HLSL)。可能的解决方案是手动创建hull和domain着色器,并使用FILES部分的文件替换语法进行注入。

目标

以下关键字是可用的

  • GLSL - 请求生成指定GLSL版本列表的源代码。请注意,列表遵循逗号分隔的qsb语法。例如,计算着色器希望在这里指定"310 es,430",因为默认值不适合它。
  • NOGLSL - 这个无参关键字禁用生成GLSL源代码。适用于所有不希望使用OpenGL的应用程序。
  • HLSL - 请求生成指定HLSL(着色器模型)版本列表的源代码。qsb工具遵循GLSL风格的版本号,因此50对应于着色器模型5.0,51是5.1。
  • NOHLSL - 这个无参关键字禁用生成HLSL源代码。适用于所有不希望使用Direct 3D的应用程序。
  • MSL - 请求生成指定版本的Metal着色语言(MSL)的源代码。12对应于1.2,20对应于2.0。
  • NOMSL - 这个无参关键字禁用生成MSL源代码。适用于所有不希望使用Metal的应用程序。
  • TESSELLATION - 这个无参关键字表示着色器用于使用扰动的管道。这仅在列出了顶点着色器并且没有禁用Metal着色器生成时相关。参见此代码片段以获取示例。

    此选项自Qt 6.5开始引入。

  • TESSELLATION_VERTEX_COUNT - 此选项接受一个数字,表示从扰动控制阶段生成的输出顶点数。对于与Metal一起使用的扰动评估着色器,指定此选项是强制性的。默认值是3。如果与扰动控制阶段的顶点不匹配,生成的MSL代码将无法按预期工作。

    此选项自Qt 6.5开始引入。

  • TESSELLATION_MODE - 该选项指定了细分模式。它可以取两个值之一:"triangles"(三角形)"quads"(四边形)。默认值是triangles。必须指定该选项,当细分控制着色器在FILES列表中时。它必须匹配细分评估阶段。

    此选项自Qt 6.5开始引入。

  • VIEW_COUNT - 该选项指定了一个顶点着色器所使用的视图数量。在处理多视图(GL_OVR_multiview2、VK_KHR_multiview、D3D12视图实例化等)时,必须为相关着色器指定正确的VIEW_COUNT,其值应大于等于2,以便生成正确的GLSL着色器代码。请注意,对于不依赖于多视图的顶点着色器,应避免设置VIEW_COUNT,因为设置该值会使生成的GLSL代码依赖于多视图。为了克服这一点,应将顶点着色器相应地分成多个qt_add_shaders()调用。设置VIEW_COUNT会自动将一个预处理定义QSHADER_VIEW_COUNT注入到着色器源代码中,其值与VIEW_COUNT相同。此外,当设置为2或更多时,会自动注入#extension GL_EXT_multiview : require行。

    该选项是在Qt 6.7中引入的。

常用的覆写设置是GLSL。例如,如果应用程序的着色器使用OpenGL 3.x功能,则可能希望指定比100 es120更高的值。

qt_add_shaders(exampleapp "res_gl3shaders"
    GLSL "300es,330"
    PREFIX
        "/shaders"
    FILES
       shaders/ssao.vert
       shaders/ssao.frag
       shaders/skybox.vert
       shaders/skybox.frag
)

注意:es后缀前面的空格是可选的。

Qt Quick特定内容

  • BATCHABLE - 指定这个无参数关键词对于与Qt Quick一起使用的顶点着色器至关重要,无论是用于ShaderEffect还是用于QSGMaterialShader。它对片段或计算着色器没有影响,并且可以将不同类型的着色器安全地包含在同一列表中,因为只考虑.vert文件的分配关键字。等同于qsb-b参数。
  • ZORDER_LOC - 当指定BATCHABLE时,默认会注入一个位置为7的额外顶点输入。此关键词用于将该位置更改为其他值。如果顶点着色器有很多输入且7已被占用,则会发生冲突。

调用外部工具

  • PRECOMPILE - 与qsb-c-t选项等价,具体取决于平台。在Windows上构建时,这会导致在构建时而不是在运行时,通过Windows SDK调用fxc来完成编译的第一阶段(HLSL源到DXBC字节码)。生成的.qsb文件将只包含编译结果(中间着色器格式),不包括原始着色器源代码。
  • OPTIMIZED - 调用spirv-opt(必须在Vulkan SDK或其他地方可用)对SPIR-V字节码进行优化。等同于qsb-O参数。

其他设置

  • DEFINES - 定义在着色器编译过程中激活的宏。等同于qsb-D参数。列表的形式为"name1=value1;name2=value2"。或者,像FILES一样,列表可以由换行符分隔。
  • 输出 - 当生成的 .qsb 文件名需要与源文件不同时,例如由于通过 定义 来区分,这使得一个着色器文件可以作为多个 .qsb 文件的源文件时,此列表可以包含 文件 列表中的每个项目的条目,指定一个以 .qsb 结尾的文件名。指定的名称随后传递给 qsb 的 -o 参数,而不是只将 .qsb 添加到源文件名中。
  • 调试信息 - 启用生成 SPIR-V 的完整调试信息,从而使工具如 RenderDoc 能够在检查管线或执行顶点或片段调试时显示完整源代码。相当于 qsb-g 参数。在指定了 PRECOMPILE 关键字的情况下,对 Direct 3D 也有影响,这时 fxc 将被指示在生成的中间字节码中包含调试信息。
  • 安静 - 抑制 qsb 的调试和警告输出。只有致命错误会被打印。
  • 输出目标 - 在使用 qt_add_shaders 与静态库一起使用时,将会生成一个或多个特殊的目标。如果您想对这些目标进行进一步处理,请将一个值传递给 OUTPUT_TARGETS 参数。

替代手动着色器

CMake 集成也支持指定 .qsb 文件中特定版本的着色器替换。实际上,这与运行 qsb 带有 -r 命令行选项的效果相同。

这是通过 FILE 列表中的以下特殊语法来启用的

FILES
    "shaders/externalsampler.frag@glsl,100es,shaders/externalsampler_gles.frag"

可以跟在文件名后面任意数量的以 @ 分隔的替换规范。每个规范指定了着色语言、版本以及从哪个文件中读取数据的文件,这些规范之间用逗号分隔。有关详细信息,请参阅 QSB 手册

细分示例

考虑一个由四个阶段组成的图形管线,包括具有着色器 vertex.vert 的顶点阶段,具有着色器 tess.tesc 的细分控制阶段,具有着色器 tess.tese 的细分评估阶段,以及具有着色器 fragment.frag 的片段阶段。

为了构建一个能够与任何 Vulkan、OpenGL、Metal 和 Direct 3D 一起工作的可移植应用程序,主要有两点需要注意:必须手动创建细分着色器的高速汇编语言版本并将其注入。对于 Metal 而言,必须指定适当的关键字。

首先,列出顶点和片段着色器。为了支持 Metal,添加了 TESSELLATION 关键字。这使 vertex.vert 在生成 Metal 着色器代码时能够接受特殊处理和转换。对于 OpenGL,由于仅在新版本的 OpenGL中有细分支持,所以我们限制了 GLSL 语言版本。

qt6_add_shaders(project "shaders_tessellation_part1"
    PREFIX
        "/shaders"
    GLSL
        "410,320es"
    TESSELLATION
    FILES
        "vertex.vert"
        "fragment.frag"
)

其次,细分着色器通过单独的 qt6_add_shaders() 调用来列出。这是由于 NOHLSL 关键字。虽然顶点和片段着色器应像往常一样将其转换为 HLSL,但是将所有四个着色器保持在单个 qt6_add_shaders() 调用中没有实现。对于 Metal,还需要指定一些细分设置(输出顶点数、模式),因为与 Vulkan 和 OpenGL 不同,这些需要在前期知晓。

qt6_add_shaders(project "shaders_tessellation_part2"
    PREFIX
        "/shaders"
    NOHLSL
    GLSL
        "410,320es"
    TESSELLATION_VERTEX_COUNT
        3
    TESSELLATION_MODE
        "triangles"
    FILES
        "tess.tesc@hlsl,50,tess_hull.hlsl"
        "tess.tese@hlsl,50,tess_domain.hlsl"
)

注意:仅推荐高级用户手动编写 hull 和 domain HLSL 着色器。某些结构,例如常数缓冲区,需要特别关注,以确保所有资源接口和布局与 SPIR-V/GLSL/MSL 着色器保持兼容。

© 2024 Qt公司有限公司。本文件中包含的文档贡献作品为各自所有者的版权。本文件提供的文档受自由软件基金会发布的GNU自由文档许可证1.3版条款许可。Qt及其相关标志是芬兰及全球其他国家的The Qt Company Ltd.的商标。所有其他商标均为各自所有者的财产。