RetroArch 内置 glslang:GLSL/HLSL 着色器到 SPIR-V 的编译管线与集成实践

RetroArch 内置 glslang:GLSL/HLSL 着色器到 SPIR-V 的编译管线与集成实践 RetroArch 内置 glslangGLSL/HLSL 着色器到 SPIR-V 的编译管线与集成实践【免费下载链接】RetroArchCross-platform, sophisticated frontend for the libretro API. Licensed GPLv3.项目地址: https://gitcode.com/GitHub_Trending/re/RetroArch导读glslang 是 Khronos 官方维护的 GLSL/ESSL 着色器前端与校验器也是把 GLSL/HLSL 源码翻译成 SPIR-V 二进制模块的事实标准编译链。本文以 RetroArch 仓库内 vendored 的 glslang 源码树deps/glslang/glslang/README.md为核心骨架完整梳理它的四个组成部件GLSL/ESSL 前端、HLSL 前端、SPIR-V 后端、glslangValidator独立命令行工具并深入它在 RetroArch 中的真实集成方式内置 glslang 如何把 Slang/GLSL 着色器在运行时编译成 SPIR-V再交给 Vulkan/GL 后端或 SPIRV-Cross 做二次转换。读完本文你将掌握 glslang 的构建、命令行验证、C/C 两种编程接口以及它在 RetroArch 着色器栈中的调用链与资源限制配置。认识 glslangKhronos 的 GLSL/ESSL 参考前端与校验器glslang 的定位在仓库文档中写得很明确“An OpenGL and OpenGL ES shader front end and validator”——它既是参考实现性质的语言前端也是一台完整的着色器编译器。它不直接为 OpenGL 驱动服务而是承担“源码 → AST → SPIR-V”这条中间链路的全部工作其核心组件可以划分为四块对应 deps/glslang/glslang/README.md 中的第 14 项GLSL/ESSL 前端对 GLSL 与 OpenGL ES Shading Language 做参考校验并把源码翻译成 AST抽象语法树HLSL 前端将广义的高层语言HLSL翻译进同一套 AST用于把 Direct3D 时代的着色器迁往 VulkanSPIR-V 后端把 AST 翻译为 SPIR-V 二进制中间表示独立命令行封装glslangValidator把上述能力打包成可直接使用的命令行工具。四者之间的数据流是单向的源码 → 前端(词法/语法分析) → AST → 后端(SPIR-V 生成)。RetroArch 正是复用了这套完整链路而没有自己另写一套 GLSL 编译器。为什么 RetroArch 需要内置 glslangRetroArch 是跨平台的 libretro 前端其着色器系统需要同时面向 Vulkan、OpenGL 等不同图形后端。要在一套着色器源码上支撑多种后端业界通行做法是把 GLSL/Slang 源码先编译成SPIR-V厂商无关的中间表示再由SPIRV-Cross把 SPIR-V 反向转译成各后端所需的 GLSL/HLSL/MSL。这条“glslang → SPIR-V → SPIRV-Cross”链路在 RetroArch 源码中有清晰实现痕迹编译侧gfx/drivers_shader/glslang.cpp直接#include了 vendored 的 ShaderLang.h 与 GlslangToSpv.h完成 GLSL→SPIR-V 编译转译侧gfx/drivers_shader/shader_gl3.c与shader_vulkan.c中的“SPIRV cross-compile (CPU)”注释与glslang_compile_shader()调用说明 SPIR-V 会继续被 SPIRV-Cross 消费转换为目标后端可链接的 GLSL汇集侧griffin/griffin_glslang.cpp将 vendored glslang 的约二十个.cpp源文件SPIRV 后端、MachineIndependent 前端、preprocessor 预处理、OGLCompilersDLL 等与 RetroArch 的 C ABI 桥接层一次性 amalgamate 进同一个翻译单元构成“builtin glslang”库。从 Makefile.common 的构建规则可见RetroArch 会编译gfx/drivers_shader/glslang.o、glslang.cpp、GlslangToSpv.cpp、SpvBuilder.cpp以及MachineIndependent与preprocessor目录下的全部源码并引入OSDependent/Unix/ossource.cpp或 Windows 版等平台相关文件。glslangValidator独立命令行验证工具基本用法构建完成后执行glslangValidator并传入一个着色器文件工具会打印警告/错误并在需要时输出 AST。基础调用形式为glslangValidator shader-file按文件扩展名自动识别着色器阶段glslangValidator不要求用户显式指定阶段它依据文件扩展名自动套用对应阶段的规则此行为直接对应 deps/glslang/glslang/README.md 的“Execution of Standalone Wrapper”一节扩展名着色器阶段.vert顶点着色器vertex shader.tesc细分控制着色器tessellation control shader.tese细分评估着色器tessellation evaluation shader.geom几何着色器geometry shader.frag片元着色器fragment shader.comp计算着色器compute shader此外还有一个非着色器扩展名扩展名用途.conf资源限制limits配置文件运行glslangValidator查看 usage 说明即可得到示例补充说明阶段规则仅由扩展名决定因此给文件起名时必须使用上表中的扩展名否则工具无法判断该按哪一阶段解析.conf文件用于在无TBuiltInResource编程配置的环境下以文本形式声明硬件/驱动资源上限等价于源码中TBuiltInResource结构的文本化表达。构建 glslang依赖、配置与安装依赖清单构建 glslang 需要对应文档 Dependencies 一节C11 编译器MSVC 场景下官方推荐 20152013 为完整支持/测试版本2010 有尝试支持但未经测试CMake用于生成编译目标makeLinux 下默认构建工具也可配置为 ninjaPython 2.7仅用于执行 SPIRV-Tools 相关脚本若不用 SPIRV-Tools 则非必需bison可选仅当修改语法文件glslang.y时需要googletest可选但凡是改动 glslang 代码都应启用测试。构建步骤以 Bash shell 为例1) 检出项目cd parent of where you want glslang to be git clone https://github.com/KhronosGroup/glslang.git2) 检出外部项目cd the directory glslang was cloned to, External will be a subdirectory git clone https://github.com/google/googletest.git External/googletest如果希望保证“从 HLSL 生成的 SPIR-V 对 Vulkan 合法”或者想对 HLSL/GLSL 使用-Os选项压缩 SPIR-V 体积还需要安装 SPIRV-Tools./update_glslang_sources.py这个脚本在仓库根目录即可找到update_glslang_sources.py它会拉取并配置 SPIRV-Tools 与 SPIRV-Headers。3) CMake 配置设源码目录为$SOURCE_DIR、构建目录为$BUILD_DIR先创建并进入构建目录mkdir -p $BUILD_DIR cd $BUILD_DIRLinux 下配置CMAKE_BUILD_TYPE可选Debug/Release/RelWithDebInfocmake -DCMAKE_BUILD_TYPE{Debug|Release|RelWithDebInfo} \ -DCMAKE_INSTALL_PREFIX$(pwd)/install $SOURCE_DIRWindows 下配置cmake $SOURCE_DIR -DCMAKE_INSTALL_PREFIX$(pwd)/install # CMAKE_INSTALL_PREFIX 部分用于后续测试后文说明-DCMAKE_INSTALL_PREFIX之所以重要是因为Test/runtests脚本要求编译产物安装到$BUILD_DIR/install目录下文档同时注明 Windows 下 CMake GUI3.4.1 版本测试通过也可正常工作。4) 构建与安装# Linux: make -j4 install # Windows: cmake --build . --config {Release|Debug|MinSizeRel|RelWithDebInfo} \ --target install使用 MSVC 时配置完成后需在 Configuration Manager 中勾选INSTALL项目才会生成安装产物。关于二进制分发文档提示除手动构建外也可以直接下载 master 分支构建机自动上传的二进制master-tot release这些二进制在每次测试通过后自动更新始终对应 master 分支最新代码。修改 GLSL 语法bison 重建 glslang.yGLSL 的语法定义在 glslang.y。该文件改动后必须用 bison 重新生成解析器。生成产物glslang_tab.cpp与glslang_tab.cpp.h会直接提交进仓库目的是避免每个开发者都必须在本地配置 bison——毕竟语法修改并不频繁。重建命令bison --definesMachineIndependent/glslang_tab.cpp.h \ -t MachineIndependent/glslang.y \ -o MachineIndependent/glslang_tab.cpp仓库同时提供了封装该命令的 bash 脚本 updateGrammar。Windows 用户可从 GnuWin32 获取 bison 二进制。在 RetroArch 中生成的 glslang_tab.cpp 被 griffin/griffin_glslang.cpp 直接#include进 amalgamation 构建与仓库根目录的 glslang.diff 所记录的本地化改动如Scan.cpp、ShaderLang.cpp、Pp.cpp等文件中的适配补丁一起构成 RetroArch 自用的 glslang 快照。测试体系Google Test 与 runtests 双轨glslang 的测试分两套见文档 Testing 一节gtests/Google Test 框架运行单元测试与“单着色器单线程”集成测试Test/runtests脚本运行多着色器链接测试与多线程测试。运行测试runtests要求编译产物安装到$BUILD_DIR/install因此 CMake 配置时必须带-DCMAKE_INSTALL_PREFIX否则需手工修改脚本里的路径。Google Test 测试cd $BUILD_DIR # Linux: ctest # Windows: ctest -C {Debug|Release|RelWithDebInfo|MinSizeRel} # 或直接运行测试二进制支持更细粒度的过滤 dir-to-glslangtests-in-build-dir/glslangtestsruntests脚本测试cd $SOURCE_DIR/Test ./runtests贡献测试的规则修改功能的 PR 必须附带测试结果单元测试使用 Google Test 并放入gtests/目录集成测试放入Test/目录其中包含测试输入以及存放期望结果的baseResults/子目录二者都在源码版本控制之下Google Test 会读取测试输入、编译并与baseResults/中的期望结果比对由gtests/*.FromFile.cpp注册待运行的集成测试glslangtests提供--update-mode选项提供时会将本次调用的真实输出覆盖写回baseResults/下的 golden 文件详见 gtests/README.mdruntests会在localResults/生成当前结果并与baseResults/做diff需要更新受版本控制的结果时用bumpshell 脚本把localResults/拷回baseResults/想加私有、不入库的测试用localtestlist列出即可runtests会自动读取并纳入diff/bump流程。编程接口C 类接口与 C 函数式接口glslang 提供两套“把着色器翻译成 AST”的程序化接口官方文档明确推荐新代码使用 C 类接口。StandAlone/StandAlone.cppmain()同时演示了两种用法。C Class Interface新、推荐接口位于ShaderLang.h末尾约 1/3处于glslang命名空间内核心 API 如下文档原样摘录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从这段签名可以读出 glslang 的环境建模方式setEnvInput指定输入语言GLSL 或 HLSL与客户端Vulkan 或 OpenGL以及 GLSL 版本示例为 100setEnvClient指定客户端与目标 API 版本如 Vulkan 1.0/1.1、OpenGL 4.50setEnvTarget指定目标 SPIR-V 版本1.0 或 1.3。这种三段式环境声明正是“同一套 AST 可同时面向 Vulkan/OpenGL 输出”的关键设计。C Functional Interface原始接口位于ShaderLang.h开头约 2/3因所有入口函数都以Sh开头而被称为Sh*()接口。它接收一个“compiler”回调对象ShCompile()构建出 AST 后调用该回调回调拿到 AST 后再执行某个后端。简化后的运行时调用栈为ShCompile(shader, compiler) - compiler(AST) - back end实际使用中ShCompile()还接收着色器字符串、默认版本号、警告/错误开关及其他编译选项。RetroArch 中的实践C ABI 桥接RetroArch 既没有直接用 C 接口也没有直接把 C 接口暴露给 C 代码而是包了一层极小的 C ABI。在 glslang_compile.h 中定义了enum glslang_compile_stage { GLSLANG_COMPILE_STAGE_VERTEX 0, GLSLANG_COMPILE_STAGE_FRAGMENT }; bool glslang_compile_spirv(const char *source, enum glslang_compile_stage stage, uint32_t **spirv, size_t *spirv_len);该头文件刻意保持“微型”不引入任何平台头文件因为桥接层会被 amalgamate 进 griffin_glslang.cpp 与 vendored glslang 源码同一编译单元若误引入windows.h其函数式min()/max()宏会破坏 glslang 对std::numeric_limits的使用其BOOL/INT/UINT/FLOATtypedef 又会与 glslang 生成解析器的全局 token 枚举冲突——这段注释本身就说明了 glslang 作为 C 库在嵌入 C 项目时的典型坑位。深入编译链路glslang 在 RetroArch 中的完整调用链核心实现glslang.cppgfx/drivers_shader/glslang.cpp 是 RetroArch 着色器栈中唯一必须保持 C 的翻译单元vendored glslang 只暴露 C API。其核心函数glslang_compile_spirv_impl()完整走了一遍官方编程接口glslang::TShader shader(language); shader.setStrings(src, 1); // ...preprocess... // ...parse... glslang::TProgram program; program.addShader(shader); program.link(messages); glslang::GlslangToSpv(*program.getIntermediate(language), *spirv);细节要点进程生命周期SlangProcessHolder的构造/析构分别调用glslang::InitializeProcess()与glslang::FinalizeProcess()并用一个std::mutex全局锁保护——注释解释这是为规避 glslang 的 TLS key 偶发损坏问题属于嵌入式集成中的实战经验消息标志EShMessages使用EShMsgDefault | EShMsgVulkanRules | EShMsgSpvRules即强制按 Vulkan 规则 SPIR-V 规则编译确保产物对 Vulkan 合法错误处理preprocess、parse、link 任一环节失败都会把getInfoLog()/getInfoDebugLog()打到 RetroArch 的RARCH_ERR日志前缀[Slang]产物GlslangToSpv()生成std::vectoruint32_t再以malloc拷贝交给调用方调用方负责free()同时返回 SPIR-V 字数spirv_len多线程保护文件顶部static std::mutex glslang_global_lock的注释明确写着“We dont use glslang from multiple threads, but to be sure”即 RetroArch 当前并不从多线程调用 glslang但仍加了锁兜底。资源限制TBuiltInResource 的嵌入配置glslang 编译需要一份描述“目标平台能力”的TBuiltInResource。RetroArch 没有使用默认值而是在 glslang.cpp 中逐字段手工填写了一整套面向现代桌面 GPU 的宽松上限例如顶点属性上限maxVertexAttribs 64、片元/顶点 uniform 分量maxFragmentUniformComponents 4096、maxVertexUniformComponents 4096计算着色器工作组规模maxComputeWorkGroupSizeX 1024 / Y 1024 / Z 64几何着色器输出顶点数maxGeometryOutputVertices 256细分相关maxPatchVertices 32、maxTessGenLevel 64图像/原子计数等maxImageUnits 8、maxFragmentAtomicCounters 8、maxAtomicCounterBufferSize 16384布尔能力开关则几乎全部置truenonInductiveForLoops、whileLoops、generalUniformIndexing、generalVariableIndexing等。这些数值与.conf配置文件表达的是同一类信息——若着色器超过这些上限glslang 的校验阶段会直接报错从而在编译期拦截“目标后端跑不动”的着色器。上层消费方shader_gl3.c 与 shader_vulkan.cSPIR-V 生成后两个图形后端继续处理Vulkan 路径shader_vulkan.c注释“SPIRV cross-compile (CPU)”随后调用glslang_compile_shader()。Vulkan 原生接受 SPIR-V故在支持SPIR-V直接提交时着色器可被直接交给驱动跳过 SPIRV-Cross 与驱动的 GLSL 前端见shader_gl3.c注释“Handing the SPIR-V straight to the driver skips both SPIRV-Cross and the drivers GLSL front end”。OpenGL 路径shader_gl3.c同样执行“SPIRV cross-compile (CPU)”但 GL 驱动不认 SPIR-V需要 SPIRV-Cross 反向转译回 GLSL 再编译链接。这解释了 RetroArch 为何要在运行时内置 glslang无论用户使用哪个图形后端都先从同一份 GLSL 源码统一产出 SPIR-V再按后端能力选择“直接消费 SPIR-V”或“经 SPIRV-Cross 转译”。编译路径与集成要点小结把 glslang 嵌入 RetroArch 的关键事实汇总如下关注点仓库依据说明源码位置deps/glslang/glslangvendored 完整 glslang 树含glslang/、SPIRV/、hlsl/、StandAlone/、Test/、gtests/等本地适配glslang.diff记录对扫描器、解析器、预处理器等的本地化改动桥接头glslang_compile.h仅暴露glslang_compile_spirv()一个 C 函数C 实现glslang.cpppreprocess→parse→link→GlslangToSpv 全链路含资源限制配置amalgamationgriffin_glslang.cpp把 glslang 约二十个源文件与桥接层打进同一编译单元构建规则Makefile.commonGLSLANG_SOURCES与头文件搜索路径含MachineIndependent、SPIRV、Public等目录消费方shader_gl3.c、shader_vulkan.cSPIR-V 直通 Vulkan 或经 SPIRV-Cross 转译给 GL平台支撑ossource.cpp随平台选择 Unix/Windows 版本构建 RetroArch 时glslang 以“builtin”方式随主程序一起编译HAVE_BUILTINGLSLANG宏也可以退化为链接系统安装的 glslangHAVE_GLSLANG宏两种模式在 glslang.cpp 的#include分支中可见一斑。结语glslang 作为 GLSL/HLSL 到 SPIR-V 的官方参考编译链其价值在于把“语言前端、语义校验、后端生成”三段解耦得足够干净语言前端产出信息无损的高层 ASTSPIR-V 后端基于 AST 生成目标二进制环境客户端 API 与版本则通过setEnvInput/setEnvClient/setEnvTarget显式声明。RetroArch 的集成则展示了把这样一个 C 库塞进 C 代码库的标准姿势——极小的 C ABI 桥接、运行时进程初始化、显式资源上限、以及面向多后端的“统一 SPIR-V 按需转译”策略。无论你是要在自己的项目里嵌入 glslang还是想理解 RetroArch 着色器管线的底层实现本文梳理的构建、接口、调用链与资源配置都可作为直接的切入点。【免费下载链接】RetroArchCross-platform, sophisticated frontend for the libretro API. Licensed GPLv3.项目地址: https://gitcode.com/GitHub_Trending/re/RetroArch创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考