WWDC Quick Look 💓 By SwiftGGTeam
Bring your game to Mac, Part 2: Compile your shaders

Bring your game to Mac, Part 2: Compile your shaders

观看原视频

Highlight

Metal Shader Converter 接受 DXIL 输入、输出 Metal IR,在二进制层面完成 HLSL 到 Metal 的转换,支持从传统几何着色器到现代网格着色器的全部阶段,配合离线 GPU 二进制编译可消除运行时着色器编译卡顿。

核心内容

为什么需要 Shader Converter

Metal 编译工具链可以把 Metal Shading Language(MSL)提前编译成 Metal IR,存储在 Metal 库中。但来自其他平台的开发者已经有大量 HLSL 着色器,需要一条迁移路径。

Metal Shader Converter 填补了这个缺口。它消费 DXIL(DirectX Intermediate Language),产出 Metal IR。配合开源 DXC 编译器,形成完整的 HLSL → DXIL → Metal IR 管线。

转换在二进制层面进行,速度极快,能显著减少着色器资产的构建时间。

01:27

支持的着色器阶段

Metal Shader Converter 覆盖了现代游戏用到的几乎所有着色器类型:

  • 传统图形管线:顶点着色器、片段着色器、几何着色器、曲面细分
  • 计算着色器
  • 光线追踪阶段和着色器
  • Amplification 和 Mesh 着色器

02:41

命令行使用方式

最简单的使用场景是通过终端逐个转换着色器:

# 1. 用 DXC 把 HLSL 编译成 DXIL
dxc -T ps_6_0 -E main myshader.hlsl -Fo myshader.dxil

# 2. 用 Shader Converter 把 DXIL 转成 Metal 库
metal-shaderconverter myshader.dxil -o myshader.metallib

默认输出包括:

  • 一个 Metal 库文件(.metallib
  • 一个 JSON 反射数据文件

运行时把这个 Metal 库传给 Metal 设备,加载并构建管线状态对象。

03:06

动态库集成方式

如果游戏引擎有自定义的资产构建程序,或者想在 Windows 上集成转换流程,可以用动态库版本:

// 动态库提供纯 C 接口
// 在 macOS 和 Windows 上都可用
#include <metal_shader_converter.h>

// 初始化转换器
MSCConverterRef converter = MSCCreateConverter();

// 配置转换选项
MSCConversionOptions options = {};
options.inputFormat = MSCInputFormatDXIL;
options.outputFormat = MSCOutputFormatMetalIR;

// 执行转换
MSCConvert(converter, dxilData, dxilSize, &options, &metalIR, &metalIRSize);

关键点

  • 动态库和 CLI 工具功能完全一致
  • 纯 C 接口,容易集成到现有代码库
  • macOS 和 Windows 双平台支持

04:36

资源绑定模型

HLSL 中资源通过 register 声明绑定到槽位。Metal 使用 Argument Buffer,更灵活。Shader Converter 提供两种布局模式:

自动布局:资源依次排列,最简单。

显式布局:匹配 root signature,适合分离纹理/采样器表或 bindless 场景。

// 自动布局:绑定一个 Argument Buffer,所有资源通过它引用
[encoder setVertexBuffer:argumentBuffer offset:0 atIndex:0];

// 显式布局:匹配 root signature 的结构
struct RootSignature {
    MTLResourceID* textureTable;
    float* myBuffer;
    uint32_t myConstant;
    MTLResourceID* samplerTable;
};

Argument Buffer 是 CPU 和 GPU 共享资源,写入时需要协调访问。推荐用 bump allocator:分配一个大 Metal Buffer,每帧从中子分配资源,为每帧处理中的资源做 shadow buffer。

05:22

几何和曲面细分映射到 Mesh Shader

Metal 是现代 API, viewport ID 和 amplification 让传统几何/曲面细分阶段变得不必要。但如果游戏依赖这些管线,手动转换成本很高。

Shader Converter 自动把几何和曲面细分管线映射到 Mesh Shader。工具完成复杂的映射工作,包括把传统固定功能的曲面细分器映射到 Metal IR。

Metal 今年新增能力:把 visible function 链接到 Mesh Shader 的 object 和 mesh 阶段。构建 Mesh Render Pipeline Descriptor 时,Metal 编译并链接所有 Metal IR,把所有函数烘焙到单个管线中,完全避免函数调用开销。

07:16

详细内容

离线 GPU 二进制编译

把着色器编译成 Metal 库还不够。运行时创建管线状态对象(PSO)时,Metal 库还需要被最终化成 GPU 二进制。这个步骤通常在应用启动时进行,导致加载时间变长。如果延迟到运行时按需编译,会造成帧率下降。

Metal GPU Binary Compiler 让你在构建时生成 GPU 二进制:

  1. 准备 Metal 库和管线配置脚本(JSON 格式)
  2. metal-tt 工具生成二进制归档
  3. 把二进制归档打包到应用中

JSON 管线脚本示例:

{
  "libraries": {
    "paths": [
      {"path": "ba.metallib", "label": "myMetalLib"}
    ]
  },
  "pipelines": {
    "render_pipelines": [{
      "vertex_function": "alias:myMetalLib#v",
      "fragment_function": "alias:myMetalLib#f",
      "raster_sample_count": 2,
      "color_attachments": [{
        "pixel_format": "BGRA8Unorm"
      }],
      "depth_attachment_pixel_format": "Depth32Float"
    }]
  }
}

关键点

  • libraries.paths 指定 Metal 库路径和别名
  • pipelines.render_pipelines 定义渲染管线配置
  • vertex_functionfragment_function 引用库中的函数
  • 其他字段对应 Metal API 中的管线状态描述符属性

14:28

测试二进制归档命中

发布前验证二进制归档包含所有需要的管线:

// 创建管线描述符
MTLComputePipelineDescriptor *computeDesc = [MTLComputePipelineDescriptor new];
computeDesc.binaryArchives = @[existingBinaryArchive];
computeDesc.computeFunction = computeFn;

id<MTLComputePipelineState> computePS =
    [device newComputePipelineStateWithDescriptor:computeDesc
                                          options:MTLPipelineOptionFailonBinaryArchiveMiss
                                            error:&err];

if (computePS == nil) {
    // 二进制归档缺少这个着色器的编译结果
    NSLog(@"Binary archive miss for compute shader");
}

关键点

  • MTLPipelineOptionFailonBinaryArchiveMiss 让 Metal 在归档未命中时返回 nil
  • 而不是回退到设备上编译
  • 这样可以提前发现遗漏的管线配置

16:30

多 OS 版本支持

不是所有玩家都用最新系统。为每个主要 OS 版本生成一个二进制归档:

MTLComputePipelineDescriptor *computeDesc = [MTLComputePipelineDescriptor new];

if (@available(macOS 14, *)) {
    computeDesc.binaryArchives = @[binaryArchive_macOS14];
} else {
    computeDesc.binaryArchives = @[binaryArchive_macOS13_3];
}

computeDesc.computeFunction = computeFn;
id<MTLComputePipelineState> computePS =
    [device newComputePipelineStateWithDescriptor:computeDesc
                                          options:nil
                                            error:&err];

关键点

  • 运行时检查 OS 版本
  • 选择对应的二进制归档
  • Metal 会自动从归档中选择适合当前 GPU 的二进制

17:03

核心启发

  • 做什么:用 Metal Shader Converter 批量转换现有 HLSL 着色器

    • 为什么值得做:二进制层面转换,速度极快,支持全部着色器阶段,省去手动重写
    • 怎么开始:安装 DXC 和 Shader Converter,写 shell 脚本批量处理:dxc 编译 HLSL 到 DXIL,metal-shaderconverter 转换到 Metal 库
  • 做什么:在构建时预编译 GPU 二进制,消除运行时编译卡顿

    • 为什么值得做:玩家启动游戏时不用等待着色器编译,游戏过程中也不会出现因编译导致的帧率下降
    • 怎么开始:用 metal-tt 工具配合 JSON 管线脚本,在构建阶段生成二进制归档
  • 做什么:用动态库版本把 Shader Converter 集成到现有构建管线

    • 为什么值得做:游戏引擎通常有自定义的资产构建程序,CLI 工具不方便集成
    • 怎么开始:链接 metal_shader_converter 动态库,用纯 C 接口在内存中完成转换
  • 做什么:混合使用 Converter 生成的库和原生 MSL 库

    • 为什么值得做:可以逐步迁移,先转换现有 HLSL,新功能用 MSL 编写,两者可以在同一个管线中混用
    • 怎么开始:Converter 输出的 Metal 库和 Metal 编译器生成的库格式完全相同,直接一起加载

关联 Session

评论

GitHub Issues · utterances