bgfx跨平台shader方案

在学习example工程的时候,你会发现代码里导入的shader是编译过的二进制文件(*.bin)。再打开shader存放的源码目录,会发现它不是主流shader文件格式的后缀名,而是.sc(shader c file)文件。这是bgfx自制的GLSL like的shader语言,即以GLSL为语法基础,加上一些自定义的宏作为扩展和跨平台语法替换。下文简称这门bgfx shader language为BSL。

BSL和GLSL的语法区别:

  • 每个shader文件开头必须用$input/$output写明输入/输出的数据类型,每一个数据类型需要写在varying.def.sc中来绑定语义
  • varying.def.sc这个文件是唯一且必须提供的
  • BSL的所有uniform变量均为float类型,不允许bool/int
  • 单个参数的vec2/3/4构造函数被取消了,替代物是vec2/3/4_splat;2/3/4个参数的不受影响
  • 构造矩阵需要使用mtxFromCols/mtxFromRows,指定行列
  • mul(x, y)限定为x向量乘以y矩阵
  • 更多的宏:https://github.com/bkaradzic/bgfx/blob/master/src/bgfx_shader.sh

这套方案位于tools/shaderc目录,从工程上可以分为这几块:

Bgfx的Shader编译工具会在一开始从BSL编译出GLSL或者是HLSL作为初始的两种Shader语言,然后通过SPIRV转换

  • OpenGL
    • BSL -> GLSL
  • OpenGL ES
    • BSL -> GLSL ES
  • Vulkan,WebGPU
    • BSL -> GLSL -> SPIRIV
  • DirectX
    • BSL -> HLSL
  • Metal
    • BSL -> HLSL -> SPIRIV -> MSL
  • 主机特定的渲染后端:AGC/GNM等
    • BSL -> PSSL
  • Nvindia的RHI
    • bgfx暂未实现

Shaderc.exe是编译BSL的命令行工具,以example-01-cubes编译windows下dx11的shader为例:

1
2
shaderc --type v --platform windows -p vs_4_0 -O 3 --varyingdef varying.def.sc -f vs_cubes.sc -o vs_cubes.bin
shaderc --type f --platform windows -p ps_4_0 -O 3 --varyingdef varying.def.sc -f fs_cubes.sc -o fs_cubes.bin

Shaderc命令行参数的含义:

  • type - 指定shader类型
    • v - vertex
    • f - fragment
    • c - compute
    • g - geometry
    • d - domain
    • h - hull
    • 要注意vf以外的shader不是所有平台都支持的,具体需要查阅目标平台使用的图形API版本号
  • platform - 指定转换将要使用编译后shader二进制的目标平台
    • android
      • 支持SPIRV,GLSL ES
    • asm.js
      • web平台,支持GLSL ES
    • ios
      • 支持MSL,GLSL ES
    • linux
      • 支持SPIRV,GLSL
    • orbis
      • PS平台
    • osx
      • 支持MSL,GLSL
    • windows
      • 支持HLSL,GLSL,GLSL ES,SPIRV
  • O - 0~3,指定优化等级
  • varyingdef - 指定varying.def.sc这个绑定类型和语义的描述文件
  • f - 指定输入shader文件
  • o - 指定输出二进制文件
  • 其他参数
    • 单个参数查看信息
      • v - 版本号
      • h - 帮助文档
    • 编译shader
      • 不需要写其他信息,直接使用的参数
        • verbose - 输出更多信息
        • debug - 输出调试信息
        • disasm - 生成反汇编文件
        • Werror - 警告视为错误
        • preprocess - 只做预处理,可以用来看BSL生成的GLSL长什么样
        • raw - 看起来可以用来接原生的GLSL
          • 先打开这个,然后在每个.sc文件头上加一行$raw就可以跳过预处理器,还有接下来的glsl优化
      • profile - 指定shader model,默认是GLSL ES,控制逻辑比较多,需要再细看下
        • TBD
      • bin2c - 可以把bin文件嵌入到一个.h头文件里,省去磁盘IO
      • i - 指定.sh(shader header file)的目录

目前bgfx这套方案遇到的一个问题是shader的开发效率,所有现成的HLSL或者GLSL源代码都无法直接应用,需要手动转译成BSL。比如之前提到的粒子系统需要对接别的中间件提供的shader,我们就需要把所有shader都写成是BSL,或者写一个HLSL/GLSL反向生成BSL的小工具。

我们希望实现BSL以外的其他方案能够直接使用常规的shader源代码,来减少工作量。

  • 直接使用HLSL:

    我们使用example-04-mesh这个例子来进行测试:

    • 第一步是新建一个测试文件夹,把用到的.sc代码和shaderc.exe都放好位置,目录结构应该是这样子的:
      • PlanATest
        • common
          • common.sh
          • shaderlib.sh
        • test
          • shaderc.exe
          • compile_hlsl.bat
          • vs_mesh.sc
          • fs_mesh.sc
          • varying.def.sc
          • bgfx_shader.sh
    • 为了测试省力,先用–preprocess参数让编译器将BSL转译成HLSL,再用–raw参数让编译期跳过预处理器用HLSL代码直接生成二进制文件,我的编译脚本如下:
      1
      2
      3
      4
      5
      6
      7
      set CURRENT_WORK_DIRECTORY=%~dp0
      shaderc -i %CURRENT_WORK_DIRECTORY% --preprocess --type v --platform windows -p vs_4_0 -O 3 --varyingdef varying.def.sc -f vs_mesh.sc -o vs_mesh.hlsl
      shaderc -i %CURRENT_WORK_DIRECTORY% --preprocess --type f --platform windows -p ps_4_0 -O 3 --varyingdef varying.def.sc -f fs_mesh.sc -o fs_mesh.hlsl

      shaderc -i %CURRENT_WORK_DIRECTORY% --raw --type v --platform windows -p vs_4_0 -O 3 -f vs_mesh.hlsl -o vs_mesh_raw_hlsl.bin
      shaderc -i %CURRENT_WORK_DIRECTORY% --raw --type f --platform windows -p ps_4_0 -O 3 -f fs_mesh.hlsl -o fs_mesh_raw_hlsl.bin
      pause
    • 复制粘贴到..\bgfx\examples\runtime\shaders\dx11目录下测试,注意确认下你的渲染后端是选择了dx11,不是的话可以改一下bgfx::init的调用代码,把type强制设置为dx11,注意我这里生成的bin文件特意加了_raw_hlsl的后缀,顺带修改下加载shader名称的代码
      1
      2
      bgfx::Init init;
      init.type = bgfx::RendererType::Direct3D11;
      1
      m_program = loadProgram("vs_mesh_raw_hlsl", "fs_mesh_raw_hlsl");
    • 编译执行,可以正常使用
  • 直接使用GLSL

    • 从compile_hlsl.bat复制一份compile_glsl.bat,修改shade model参数和输入输出的文件名:
      1
      2
      3
      4
      5
      6
      7
      set CURRENT_WORK_DIRECTORY=%~dp0
      shaderc -i %CURRENT_WORK_DIRECTORY% --preprocess --type v --platform windows -p 150 -O 3 --varyingdef varying.def.sc -f vs_mesh.sc -o vs_mesh.glsl
      shaderc -i %CURRENT_WORK_DIRECTORY% --preprocess --type f --platform windows -p 150 -O 3 --varyingdef varying.def.sc -f fs_mesh.sc -o fs_mesh.glsl

      shaderc -i %CURRENT_WORK_DIRECTORY% --raw --type v --platform windows -p 150 -O 3 -f vs_mesh.glsl -o vs_mesh_raw_glsl.bin
      shaderc -i %CURRENT_WORK_DIRECTORY% --raw --type f --platform windows -p 150 -O 3 -f fs_mesh.glsl -o fs_mesh_raw_glsl.bin
      pause
    • 复制粘贴丢到..\bgfx\examples\runtime\shaders\glsl目录下测试,这里Windows系统必须主动去把bgfx::init调用强制改成OpenGL
      1
      2
      bgfx::Init init;
      init.type = bgfx::RendererType::OpenGL;
      1
      m_program = loadProgram("vs_mesh_raw_glsl", "fs_mesh_raw_glsl");
    • 编译执行,可以正常使用
  • 更多

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    static const Profile s_profiles[] =
    {
    { ShadingLang::ESSL, 100, "100_es" },
    { ShadingLang::ESSL, 300, "300_es" },
    { ShadingLang::ESSL, 310, "310_es" },
    { ShadingLang::ESSL, 320, "320_es" },
    { ShadingLang::HLSL, 300, "s_3_0" },
    { ShadingLang::HLSL, 400, "s_4_0" },
    { ShadingLang::HLSL, 500, "s_5_0" },
    { ShadingLang::Metal, 1000, "metal" },
    { ShadingLang::PSSL, 1000, "pssl" },
    { ShadingLang::SpirV, 1311, "spirv13-11" },
    { ShadingLang::SpirV, 1411, "spirv14-11" },
    { ShadingLang::SpirV, 1512, "spirv15-12" },
    { ShadingLang::SpirV, 1010, "spirv10-10" },
    { ShadingLang::SpirV, 1010, "spirv" },
    { ShadingLang::GLSL, 120, "120" },
    { ShadingLang::GLSL, 130, "130" },
    { ShadingLang::GLSL, 140, "140" },
    { ShadingLang::GLSL, 150, "150" },
    { ShadingLang::GLSL, 330, "330" },
    { ShadingLang::GLSL, 400, "400" },
    { ShadingLang::GLSL, 410, "410" },
    { ShadingLang::GLSL, 420, "420" },
    { ShadingLang::GLSL, 430, "430" },
    { ShadingLang::GLSL, 440, "440" },
    };

问题记录:

  • 本来是打算用example-01-cubes作为例子,但是它用到了bgfx预定义的uniform变量里的u_modelViewProj这个mvp转换矩阵,这是我生成的hlsl里没有声明的,还需要额外把这些预定义uniforms的声明文件也找出来加进去,这是个很好解决的小问题。
  • 上述试验使用的是没有经过编译的glsl或者hlsl直接传进去用,可以再试一下拿glslang或者dxc去编译完再丢进去
  • 直接使用SPIRV字节码还需要看一下怎么传
  • bgfx对于DirectX12使用的shader同DirectX11,没有走DXIL(DirectX Intermediate Language)

其他的shader跨平台方案

参考资料