用户可使用该项目进行 GLSL、ESSL、HLSL 着色器的编译、验证及转换为 SPIR-V 中间语言。核心功能包括参考级验证前端、AST 转换后端、反射信息提取及命令行工具,支持多平台构建与测试。【此简介由AI生成】
最新动态
-
--shift-texture-binding[s]选项不再影响组合采样器。应使用新的--shift-combined-sampler-binding[s]选项来独立于单独纹理控制组合采样器的绑定。将这两个选项设置为相同值可实现旧有行为。 -
glslang 中的 spirv-remap 工具已移植到 SPIRV-Tools 代码库,作为名为 canonicalize-ids 的新优化通道,可在 spirv-opt 中使用。有关使用详情,请参见 spirv-opt --help。
-
现在可以将 glslang 构建为 DLL 或共享库,并且对此提供支持。
Glslang 组件及状态
包含以下几个组件:
参考验证器与 GLSL/ESSL -> AST 前端
一个 OpenGL GLSL 和 OpenGL|ES GLSL(ESSL)前端,用于参考验证以及将 GLSL/ESSL 转换为内部抽象语法树(AST)。
状态:基本完成,其结果与规范具有同等重要性。
HLSL -> AST 前端
一个 HLSL 前端,用于将 HLSL 的近似版本转换为 glslang 的 AST 形式。
状态:部分完成。语义并非参考级质量,输入未经过验证。 这与 DXC 项目 形成对比,后者获得了更大投入,并致力于实现明确的/参考级语义。
有关当前状态,请参见 issue 362 和 issue 701。
AST -> SPIR-V 后端
将 glslang 的 AST 转换为 Khronos 规定的 SPIR-V 中间语言。
状态:基本完成。
反射器
一个用于从AST获取反射信息的API,可从高级语言(HLL)源代码(非SPIR-V)中反射类型/变量等信息。
状态:已实现大量功能,但尚无衡量完整性的规范/目标。对于输入的高级语言和AST而言是准确的,但对于后续将生成的SPIR-V内容仅为近似值。
独立包装器
glslang是一个用于访问上述功能的命令行工具。
状态:已完成。
待完成任务记录在GitHub issues中。
其他参考资料
另请参阅Khronos上关于glslang作为参考前端的登录页面:
https://www.khronos.org/opengles/sdk/tools/Reference-Compiler/
上述页面虽未保持最新,但包含了有关glslang作为参考验证器的其他信息。
如何使用Glslang
独立包装器的执行
要使用独立二进制形式,请执行glslang,它将打印使用说明。基本操作是向其提供包含着色器的文件,它将输出警告/错误,并可选择输出AST。
应用的特定阶段规则基于文件扩展名:
.vert用于顶点着色器.tesc用于细分控制着色器.tese用于细分评估着色器.geom用于几何着色器.frag用于片段着色器.comp用于计算着色器
对于光线追踪管线着色器:
.rgen用于光线生成着色器.rint用于光线相交着色器.rahit用于光线任意命中着色器.rchit用于光线最近命中着色器.rmiss用于光线未命中着色器.rcall用于可调用着色器
还有一个非着色器扩展名:
.conf用于限制的配置文件,示例见使用说明
构建(CMake)
除了手动构建外,您还可以直接从GitHub上的main-tot release下载适用于您平台的二进制文件。这些二进制文件由构建机器人在测试成功后自动上传,始终反映main分支当前的最新代码。
依赖项
- 一个 C++17 编译器。 (对于 MSVS:使用 2019 或更高版本。)
- CMake:用于生成编译目标。
- make:Linux,如果已配置,ninja 可作为替代方案。
- Python 3.x:用于执行 SPIRV-Tools 脚本。(如果不使用 SPIRV-Tools 且“External”子目录不存在,则为可选。)
- bison:可选,但在更改语法(glslang.y)时需要。
- googletest:可选,但如果对 glslang 进行任何更改,建议使用。
构建步骤
以下步骤假设使用 Bash shell。在 Windows 上,可以使用 Git Bash shell 或其他您选择的 shell。
1) 检出此项目
cd <parent of where you want glslang to be>
git clone https://github.com/KhronosGroup/glslang.git
2) 检出外部项目
./update_glslang_sources.py
3) 配置
假设源目录为 $SOURCE_DIR,构建目录为 $BUILD_DIR。
如果 $BUILD_DIR 不存在,CMake 会为用户创建该目录。
首先更改工作目录:
cd $SOURCE_DIR
在 Linux 上构建:
cmake -B $BUILD_DIR -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="$(pwd)/install"
# "Release" (for CMAKE_BUILD_TYPE) could also be "Debug" or "RelWithDebInfo"
在 Android 上构建:
cmake -B $BUILD_DIR -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX="$(pwd)/install" -DANDROID_ABI=arm64-v8a -DCMAKE_BUILD_TYPE=Release -DANDROID_STL=c++_static -DANDROID_PLATFORM=android-24 -DCMAKE_SYSTEM_NAME=Android -DANDROID_TOOLCHAIN=clang -DANDROID_ARM_MODE=arm -DCMAKE_MAKE_PROGRAM=$ANDROID_NDK_HOME/prebuilt/linux-x86_64/bin/make -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake
# If on Windows will be -DCMAKE_MAKE_PROGRAM=%ANDROID_NDK_HOME%\prebuilt\windows-x86_64\bin\make.exe
# -G is needed for building on Windows
# -DANDROID_ABI can also be armeabi-v7a for 32 bit
在 Windows 上构建:
cmake -B $BUILD_DIR -DCMAKE_INSTALL_PREFIX="$(pwd)/install"
# The CMAKE_INSTALL_PREFIX part is for testing (explained later).
此外,在 Windows 上可考虑使用 git config --global core.fileMode false(或使用 --local)来防止文件被添加执行权限。
4) 构建和安装
# for Linux:
make -j4 install
# for Windows:
cmake --build . --config Release --target install
# "Release" (for --config) could also be "Debug", "MinSizeRel", or "RelWithDebInfo"
如果使用 MSVC,运行 CMake 完成配置后,请使用配置管理器检查 INSTALL 项目。
构建(GN)
glslang 也可以通过 GN 构建系统 进行构建。
1) 安装 depot_tools
下载 depot_tools.zip,将其解压到一个目录,并将该目录添加到您的 PATH 中。
2) 同步依赖项并生成构建文件
此步骤仅需在更新 glslang 后执行一次。
将当前目录设置为您的 glslang 检出目录,然后输入:
./update_glslang_sources.py
gclient sync --gclientfile=standalone.gclient
gn gen out/Default
3) 构建
将当前目录设置为您的 glslang 检出目录,然后输入:
cd out/Default
ninja
如果你需要修改 GLSL 语法
若 glslang/MachineIndependent/glslang.y 中的语法发生变更,则必须使用 bison 重新编译该语法,生成的输出文件会提交到代码库中,这样就避免了每位开发者在语法变更(这种情况并不常见)时都需要配置 bison 才能编译项目。在 Windows 系统上,你可以从 GnuWin32 获取二进制文件。
重新构建的命令如下:
bison --defines=MachineIndependent/glslang_tab.cpp.h
-t MachineIndependent/glslang.y
-o MachineIndependent/glslang_tab.cpp
上述命令也可在 updateGrammar 中的 bash 脚本中找到,需从 glslang 仓库的 glslang 子目录执行。
为 Web 和 Node 构建 WASM
为 Web 和 Node 构建独立的 JS/WASM 库
请使用构建步骤中的步骤,但需注意以下说明/例外情况:
- 你的可执行文件搜索路径(Bash 类环境中的 PATH)中需要包含
emsdk: - 使用
emcmake cmake包装 cmake 调用。 - 设置
-DENABLE_OPT=OFF。 - 如果不需要 HLSL,请设置
-DENABLE_HLSL=OFF。 - 若要构建独立的 JS/WASM 库,请开启
-DENABLE_GLSLANG_JS=ON。 - 若要获得完全最小化的构建,请确保使用
brotli压缩 .js 和 .wasm 文件。 - 注意,Emscripten 默认分配的栈大小非常小,这在编译大型着色器时可能导致栈溢出。请使用STACK_SIZE编译器设置来增加栈大小。
示例:
emcmake cmake -DCMAKE_BUILD_TYPE=Release -DENABLE_GLSLANG_JS=ON \
-DENABLE_HLSL=OFF -DENABLE_OPT=OFF ..
构建 glslang - 使用 vcpkg
您可以通过 vcpkg 依赖管理器下载并安装 glslang:
git clone https://github.com/Microsoft/vcpkg.git cd vcpkg ./bootstrap-vcpkg.sh ./vcpkg integrate install ./vcpkg install glslang
vcpkg 中的 glslang 端口由 Microsoft 团队成员和社区贡献者保持更新。如果版本已过时,请在 vcpkg 代码库上创建 issue 或 pull request。
测试
目前,glslang 中有两个测试工具:一个是 Google Test,另一个是 runtests 脚本。前者运行单元测试和单着色器单线程集成测试,而后者运行多着色器链接测试和多线程测试。
如果使用 ALLOW_EXTERNAL_SPIRV_TOOLS 时,所使用的提交不是 known_good.json 中指定的提交,则测试可能会错误地失败或通过。
运行测试
runtests 脚本 要求已编译的二进制文件安装到 $BUILD_DIR/install。请确保在构建时已向 CMake 提供了正确的配置(使用 -DCMAKE_INSTALL_PREFIX);否则,您可能需要修改 runtests 脚本中的路径。
运行基于 Google Test 的测试:
cd $BUILD_DIR
# for Linux:
ctest
# for Windows:
ctest -C {Debug|Release|RelWithDebInfo|MinSizeRel}
# or, run the test binary directly
# (which gives more fine-grained control like filtering):
<dir-to-glslangtests-in-build-dir>/glslangtests
运行 runtests 脚本支持的测试:
cd $SOURCE_DIR/Test && ./runtests
如果某些测试因验证错误而失败,可能是系统上的 spirv-val 版本与 glslang 版本不匹配。在这种情况下,需要运行 update_glslang_sources.py。有关更多详细信息,请参见上文的“签出外部项目”。
贡献测试
修改功能的拉取请求应始终包含测试结果。
如果编写单元测试,请使用 Google Test 框架,并将测试放在 gtests/ 目录下。
集成测试位于 Test/ 目录中。该目录包含测试输入以及一个 baseResults/ 子目录,其中包含测试的预期结果。测试和 baseResults/ 均受源代码控制。
Google Test 通过读取测试输入、编译它们,然后与 baseResults/ 中的预期结果进行比较来运行这些集成测试。通过 Google Test 运行的集成测试在各种 gtests/*.FromFile.cpp 源文件中注册。glslangtests 提供了一个命令行选项 --update-mode,如果提供该选项,将使用该次调用的实际输出覆盖 baseResults/ 目录下的黄金文件。有关更多信息,请查看 gtests/ 目录的 README。
对于 runtests 脚本,它将在 localResults/ 目录中生成当前结果,并将其与 baseResults/ 进行 diff 比较。当你想要更新跟踪的测试结果时,需要将它们从 localResults/ 复制到 baseResults/。这可以通过 bump shell 脚本来完成。
你可以使用 localtestlist 列出未跟踪的测试,从而添加自己的私有测试列表(不公开跟踪)。runtests 会自动读取此文件,并将其包含在 diff 和 bump 过程中。
编程接口
其他软件可以通过以下两种不同的接口以编程方式将着色器转换为 AST:
- 新的面向 C++ 类的接口,或
- 原始的 C 函数式接口
StandAlone/StandAlone.cpp 中的 main() 展示了使用这两种风格的示例。
C++ 类接口(新的,推荐使用)
此接口大致位于 ShaderLang.h 的后 1/3 部分。它位于 glslang 命名空间中,包含以下内容,此处提供了生成 SPIR-V 的建议调用:
const char* GetEsslVersionString();
const char* GetGlslVersionString();
bool InitializeProcess();
void FinalizeProcess();
class TShader
setStrings(...);
setEnvInput(EShSourceHlsl or EShSourceGlsl, stage, EShClientVulkan or EShClientOpenGL, 100);
setEnvClient(EShClientVulkan or EShClientOpenGL, EShTargetVulkan_1_0 or EShTargetVulkan_1_1 or EShTargetOpenGL_450);
setEnvTarget(EShTargetSpv, EShTargetSpv_1_0 or EShTargetSpv_1_3);
bool parse(...);
const char* getInfoLog();
class TProgram
void addShader(...);
bool link(...);
const char* getInfoLog();
Reflection queries
仅用于验证(不生成代码)时,请替换以下调用:
setEnvInput(EShSourceHlsl or EShSourceGlsl, stage, EShClientNone, 0);
setEnvClient(EShClientNone, 0);
setEnvTarget(EShTargetNone, 0);
有关更多详细信息,请参见 ShaderLang.h 以及 StandAlone/StandAlone.cpp 中对它的使用。在 setEnvInput、setEnvClient 和 setEnvTarget 的调用上方,有一个块注释提供了更多详细信息。
C 函数接口(原始)
此接口大致位于 ShaderLang.h 的前 2/3 部分,被称为 Sh*() 接口,因为所有入口点均以 Sh 开头。
Sh*() 接口接受一个“编译器”回调对象,该对象在构建完回调后被调用,此回调会接收 AST(抽象语法树),然后可以在其上执行后端操作。
以下是简化后的运行时调用栈:
ShCompile(shader, compiler) -> compiler(AST) -> <back end>
在实际应用中,ShCompile() 接收着色器字符串、默认版本以及用于控制编译的警告/错误等选项。
C 函数接口(新增)
此接口位于 glslang_c_interface.h 中,提供与 C++ 接口类似的功能。以下代码片段是一个完整示例,展示了如何将 GLSL 编译为适用于 Vulkan 1.2 的 SPIR-V 1.5。
#include <glslang/Include/glslang_c_interface.h>
// Required for use of glslang_default_resource
#include <glslang/Public/resource_limits_c.h>
typedef struct SpirVBinary {
uint32_t *words; // SPIR-V words
int size; // number of words in SPIR-V binary
} SpirVBinary;
SpirVBinary compileShaderToSPIRV_Vulkan(glslang_stage_t stage, const char* shaderSource, const char* fileName) {
const glslang_input_t input = {
.language = GLSLANG_SOURCE_GLSL,
.stage = stage,
.client = GLSLANG_CLIENT_VULKAN,
.client_version = GLSLANG_TARGET_VULKAN_1_2,
.target_language = GLSLANG_TARGET_SPV,
.target_language_version = GLSLANG_TARGET_SPV_1_5,
.code = shaderSource,
.default_version = 100,
.default_profile = GLSLANG_NO_PROFILE,
.force_default_version_and_profile = false,
.forward_compatible = false,
.messages = GLSLANG_MSG_DEFAULT_BIT,
.resource = glslang_default_resource(),
};
glslang_shader_t* shader = glslang_shader_create(&input);
SpirVBinary bin = {
.words = NULL,
.size = 0,
};
if (!glslang_shader_preprocess(shader, &input)) {
printf("GLSL preprocessing failed %s\n", fileName);
printf("%s\n", glslang_shader_get_info_log(shader));
printf("%s\n", glslang_shader_get_info_debug_log(shader));
printf("%s\n", input.code);
glslang_shader_delete(shader);
return bin;
}
if (!glslang_shader_parse(shader, &input)) {
printf("GLSL parsing failed %s\n", fileName);
printf("%s\n", glslang_shader_get_info_log(shader));
printf("%s\n", glslang_shader_get_info_debug_log(shader));
printf("%s\n", glslang_shader_get_preprocessed_code(shader));
glslang_shader_delete(shader);
return bin;
}
glslang_program_t* program = glslang_program_create();
glslang_program_add_shader(program, shader);
if (!glslang_program_link(program, GLSLANG_MSG_SPV_RULES_BIT | GLSLANG_MSG_VULKAN_RULES_BIT)) {
printf("GLSL linking failed %s\n", fileName);
printf("%s\n", glslang_program_get_info_log(program));
printf("%s\n", glslang_program_get_info_debug_log(program));
glslang_program_delete(program);
glslang_shader_delete(shader);
return bin;
}
glslang_program_SPIRV_generate(program, stage);
bin.size = glslang_program_SPIRV_get_size(program);
bin.words = malloc(bin.size * sizeof(uint32_t));
glslang_program_SPIRV_get(program, bin.words);
const char* spirv_messages = glslang_program_SPIRV_get_messages(program);
if (spirv_messages)
printf("(%s) %s\b", fileName, spirv_messages);
glslang_program_delete(program);
glslang_shader_delete(shader);
return bin;
}
基本内部操作
-
初始词法分析由
MachineIndependent/Preprocessor中的预处理器完成,然后由MachineIndependent/Scan.cpp中的 GLSL 扫描器进行优化。目前未使用 flex。 -
代码解析通过 bison 处理
MachineIndependent/glslang.y文件,并借助符号表和 AST(抽象语法树)实现。符号表不会传递到后端;中间表示独立存在。语法产生式(其中许多已转移至ParseHelper.cpp)和Intermediate.cpp共同构建该语法树。 -
中间表示是非常高级的,以内存树的形式呈现。这有助于完整保留原始程序的所有信息,并实现从解析结果到后端的高效传输。在 AST 中,常量会进行传播和折叠,同时会消除极少量的死代码。
为辅助链接和反射,AST 中的最后一个顶级分支会列出所有全局符号。
-
后端编译器的主要算法是遍历该语法树(高级中间表示),并创建内部目标代码表示。
MachineIndependent/intermOut.cpp中提供了此类操作的示例。 -
将语法树简化为线性字节码风格的低级中间表示,可能是生成完全优化代码的有效方法。
-
目前仍存在一些过时的链接器类型死代码。
-
内存池:解析使用从 C++
std类型派生的类型,并通过自定义分配器将它们放入内存池。这使得单个容器/内容的分配仅需几个周期,且释放无需额外成本。在 AST 创建和处理完成后,该内存池会被释放。使用方法简单:当你要调用
new时,存在以下三种情况:-
对象来自内存池(其基类包含宏
POOL_ALLOCATOR_NEW_DELETE),此时无需调用delete -
对象是
TString,此时需调用NewPoolTString(),该函数从内存池获取对象,且无需对应的delete -
对象不来自内存池,此时你需要对所
new的对象进行常规的 C++ 内存管理
-
-
功能可通过版本/扩展/阶段/配置文件进行保护: 详见
glslang/MachineIndependent/Versions.cpp中的注释。
项目介绍
用户可使用该项目进行 GLSL、ESSL、HLSL 着色器的编译、验证及转换为 SPIR-V 中间语言。核心功能包括参考级验证前端、AST 转换后端、反射信息提取及命令行工具,支持多平台构建与测试。【此简介由AI生成】
定制我的领域