glslang:GLSL/HLSL编译与SPIR-V转换工具,支持多着色器阶段与反射功能

提供GLSL/ESSL参考验证、HLSL近似转换及AST到SPIR-V翻译,包含命令行工具与反射API,支持多平台构建,助力图形渲染开发。【此简介由AI生成】

分支1Tags1
文件最后提交记录最后更新时间
2 年前
11 个月前
11 个月前
11 个月前
11 个月前
3 年前
11 个月前
11 个月前
11 个月前
11 个月前
11 个月前
3 年前
3 年前
3 年前
11 个月前
11 个月前
11 个月前
11 个月前
11 个月前
3 年前
3 年前
3 年前
11 个月前
11 个月前
11 个月前
3 年前
3 年前
3 年前
3 年前
11 个月前
11 个月前
3 年前
3 年前
3 年前
11 个月前

持续集成 持续部署 OpenSSF 安全评分卡

最新动态

  1. 现已支持将 glslang 构建为 DLL 或共享库。

  2. GenericCodeGenMachineIndependentOSDependentSPIRV 库已整合至主 glslang 库中。旧版独立库现以空存根形式临时保留以兼容现有项目,未来将彻底移除。

  3. 新增 CMake 选项 ENABLE_SPIRV,用于控制是否启用 SPIR-V 支持,默认值为 ON

  4. 构建系统中已完全移除 OGLCompilerHLSL 存根库。

建议用户通过标准方式 CMAKE_MSVC_RUNTIME_LIBRARY 进行配置。

Glslang 组件与状态

包含以下核心模块:

参考验证器与 GLSL/ESSL 前端

为 OpenGL GLSL 和 OpenGL|ES GLSL (ESSL) 提供参考级验证,并将其转换为内部抽象语法树 (AST)。

状态:功能完备,验证结果与官方规范具有同等效力。

HLSL 前端

将 HLSL 近似转换为 glslang 的 AST 形式。

状态:部分实现。语义未达参考标准且输入未经验证。这与获得更多资源投入、致力于实现权威参考语义的 DXC 项目 形成对比。

当前进展详见 issue 362issue 701

AST 至 SPIR-V 后端

将 glslang 的 AST 转换为 Khronos 标准 SPIR-V 中间语言。

状态:功能完备。

反射器

提供从 AST 获取反射信息的 API,可直接从高级着色语言 (HLL) 源码(非 SPIR-V)提取类型/变量等反射数据。

状态:已实现大量功能,但缺乏完整性评估标准。对输入 HLL 和 AST 的反射准确,但对最终 SPIR-V 输出的反射仅为近似结果。

独立封装工具

glslang 作为命令行工具集成上述功能。

状态:功能完整。

待办任务记录于 GitHub issues。

其他参考

另见 Khronos 官网对 glslang 作为参考前端的说明:

https://www.khronos.org/opengles/sdk/tools/Reference-Compiler/

该页面虽未实时更新,但包含关于 glslang 作为参考验证器的补充信息。

使用指南

运行独立工具

执行 glslang 可查看使用说明。基本操作是输入着色器文件,程序将输出警告/错误信息及可选的 AST 结构。

文件扩展名决定适用的着色阶段规则:

  • .vert 顶点着色器
  • .tesc 曲面细分控制着色器
  • .tese 曲面细分评估着色器
  • .geom 几何着色器
  • .frag 片段着色器
  • .comp 计算着色器

光线追踪管线着色器扩展名:

  • .rgen 光线生成着色器
  • .rint 光线相交着色器
  • .rahit 光线任意命中着色器
  • .rchit 光线最近命中着色器
  • .rmiss 光线未命中着色器
  • .rcall 可调用着色器

非着色器扩展名:

  • .conf 配置文件(用于设置限制参数,参见使用说明示例)

构建 (CMake)

除手动构建外,也可直接从 GitHub main-tot 发布版 下载对应平台预编译二进制文件。这些文件由构建机器人测试通过后自动上传,始终与主分支最新提交同步。

依赖项

  • C++17 编译器 (MSVS 需 2019 或更新版本)
  • CMake:生成编译目标
  • make:Linux 环境可用 ninja 替代
  • Python 3.x:执行 SPIRV-Tools 脚本(若不使用 SPIRV-Tools 且无 'External' 子目录则可选)
  • bison可选,修改语法文件 (glslang.y) 时需要
  • googletest可选,建议修改 glslang 时使用

构建步骤

以下步骤基于 Bash 环境。Windows 系统可使用 Git Bash 或其他兼容 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。首先确保构建目录存在,然后进入该目录:

mkdir -p $BUILD_DIR
cd $BUILD_DIR

在 Linux 系统上构建:

cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="$(pwd)/install" $SOURCE_DIR
# "Release" (for CMAKE_BUILD_TYPE) could also be "Debug" or "RelWithDebInfo"

适用于 Android 平台的构建:

cmake $SOURCE_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 $SOURCE_DIR -DCMAKE_INSTALL_PREFIX="$(pwd)/install"
# The CMAKE_INSTALL_PREFIX part is for testing (explained later).

CMake GUI 同样适用于 Windows 系统(测试版本为 3.4.1)。

此外,建议在 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 配置完成后,请通过 Configuration Manager 检查 INSTALL 项目。

如需通过 CMake 启用测试,请在配置构建时设置 GLSLANG_TESTS=ON

默认情况下 GLSLANG_TESTS 处于关闭状态,以简化打包及 Vulkan SDK 流程。

构建(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 库

遵循构建步骤中的指引,并注意以下事项/例外:

  • 确保 emsdk 位于可执行文件搜索路径中,对于类 Bash 环境即 PATH
  • 封装 cmake 调用:emcmake 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 组件由微软团队成员和社区贡献者持续维护更新。若发现版本过时,请在 vcpkg 仓库中提交问题或拉取请求

测试

目前 glslang 包含两套测试框架:一套基于 Google Test,另一套是 runtests 脚本。前者用于运行单元测试及单着色器单线程集成测试,后者则用于多着色器链接测试和多线程测试。

若在 ALLOW_EXTERNAL_SPIRV_TOOLS 启用状态下使用非 known_good.json 指定版本的提交,可能导致测试结果异常通过或失败。

执行测试

runtests 脚本 要求将编译后的二进制文件安装至 $BUILD_DIR/install 目录。请确保在构建时已通过 CMake 正确配置安装路径(使用 -DCMAKE_INSTALL_PREFIX 参数),否则可能需要修改脚本中的路径设置。

执行基于 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 脚本完成。

您可以通过 localtestlist 添加自己的私有测试列表(不公开跟踪)。runtests 会自动读取这些测试,并包含在 diffbump 流程中。

编程接口

其他软件可以通过以下两种接口以编程方式将着色器转换为抽象语法树(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 中的使用示例。在调用 setEnvInputsetEnvClientsetEnvTarget 的上方有一段详细说明的块注释。

C 函数式接口(原始)

该接口大致位于 ShaderLang.h 文件的前三分之二部分,被称为 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操作

    • 对象不来自内存池,需按标准C++内存管理方式处理new操作

  • 功能特性可通过版本/扩展/阶段/配置文件进行保护:详见glslang/MachineIndependent/Versions.cpp中的注释说明。

项目介绍

提供GLSL/ESSL参考验证、HLSL近似转换及AST到SPIR-V翻译,包含命令行工具与反射API,支持多平台构建,助力图形渲染开发。【此简介由AI生成】

定制我的领域