Bring your game to Mac, Part 3: Render with Metal
Graphics & Games 进阶 20m

将游戏移植到 Mac,第三部分:用 Metal 渲染

Bring your game to Mac, Part 3: Render with Metal

2023年6月5日

在 Apple 官方观看视频

一句话判断

如果你在把 Windows 游戏引擎移植到 Mac,这场 Session 给出的 Metal 资源管理和命令提交策略可以直接当工程指南用——特别是 Bindless 模式和 Apple GPU TBDR 架构的适配部分。

这场 Session 讲了什么

这是”将游戏移植到 Mac”系列三场 Session 中的第三场,聚焦在渲染器层面的 Metal 适配。

四个核心议题:

1. 资源绑定(Resource Bindings): Metal Shader Converter 提供两种绑定模式——Automatic Layout(自动生成)和 Explicit Layout(手动指定)。Explicit Layout 可以映射其他平台的绑定模型,比如 Direct3D 的 Root Signature。Metal 的 Argument Buffer 比 Descriptor Table 更灵活——支持混合类型元素。Session 展示了如何用 Argument Buffer 完整编码 Root Signature 的四个组成部分:纹理描述符表、缓冲区参数、32 位常量、采样器描述符表。

2. 资源驻留与同步(Residency & Synchronization): Bindless 模式下需要显式管理资源驻留。核心建议:只读资源归入大 Heap,每个编码器调用一次 useHeap;可写资源单独分配,每个编码器调用 useResource 并指定使用标志。可写资源让 Metal 处理同步,避免手动同步的复杂性。

3. 命令提交优化: Apple GPU 是 TBDR(Tile-Based Deferred Renderer)架构,有统一内存和片上 Tile Memory。Metal 使用 Pass 概念,引擎需要将渲染命令按类型(Graphics/Compute/Blit)分组到不同的 Pass 中。

4. MetalFX 超分辨率: 渲染到低分辨率后由 MetalFX 超采样到目标分辨率,节省每帧渲染时间。

值得深挖的点

Read-only 和 Writable 资源的不同管理策略是这场的精华。只读资源用 Heap 统一管理,一次 useHeap 就能让所有资源驻留,CPU 开销极低。可写资源单独分配,让 Metal 自动处理 hazard tracking 和同步——Apple 明确建议不要手动做可写资源的同步,因为这件事”复杂且耗时”。这种分工让 Bindless 模式的实现成本大大降低。

从 Root Signature 到 Argument Buffer 的映射给出了具体的代码路径。Root Signature 中的每个 Descriptor Table 对应一个 Metal Buffer,存储纹理或采样器的 resource ID。顶层 Root Signature 本身也是一个 Argument Buffer,包含 GPU 地址指向各子表。整个过程可以在渲染循环外完成,运行时只需绑定顶层 Argument Buffer。

TBDR 架构的 Pass 分组要求对从连续命令流模型(如 Direct3D 12 或 Vulkan)迁移的引擎来说是必须理解的差异。Metal 要求命令按类型分组为 Pass,不能混合。这不是限制,而是利用 Tile Memory 的前提——正确的 Pass 分组能让渲染操作在片上完成,避免昂贵的系统内存回写。

代码片段

编码纹理描述符表(对应 Root Signature 中的 Descriptor Table):

// 创建 Metal Buffer 作为纹理描述符表
// 存储每个纹理的 resource ID
MTL::Buffer* textureTable = device->newBuffer(
    textureCount * sizeof(MTL::ResourceID),
    MTL::ResourceStorageModeShared
);

// 创建每个纹理时,将其 resourceID 存入描述符表
for (int i = 0; i < textureCount; i++) {
    auto texture = device->newTexture(textureDescriptor);
    auto resourceId = texture->gpuResourceID();
    // 将 resourceID 写入表的对应位置
    ((MTL::ResourceID*)textureTable->contents())[i] = resourceId;
}
// 注意:这段代码在渲染循环外执行,只需初始化一次

资源驻留管理:

// 只读资源:统一放入 Heap,一次性驻留
auto heap = device->newHeap(heapDescriptor);
// 从 heap 分配所有只读资源
auto texture = heap->newTexture(textureDescriptor);
// 渲染时每个编码器调用一次
renderEncoder->useHeap(heap);

// 可写资源:单独分配,Metal 自动处理同步
auto writeTexture = device->newTexture(writeDescriptor);
// 渲染时指定使用标志
renderEncoder->useResource(writeTexture, MTL::ResourceUsageWrite);
renderEncoder->useResource(readBuffer, MTL::ResourceUsageRead);

最佳实践

  • 使用 Metal Shader Converter 的 Explicit Layout 模式移植现有绑定模型,减少引擎改造量。
  • 只读资源统一放入大 Heap,设置 hazard tracking mode 为 Untracked。运行时每个编码器只调用一次 useHeap
  • 可写资源不要手动同步。让 Metal 的 hazard tracking 处理跨编码器的同步,这是 Apple 推荐的做法。
  • 命令提交时按类型分组为 Pass,不要在同一个 Pass 中混合 Graphics 和 Compute 命令。
  • 利用 MetalFX 超分辨率降低渲染分辨率,在 Apple Silicon 上可以获得显著的性能提升。
  • Root Signature 的编码可以在初始化阶段完成,渲染循环中只需绑定顶层 Argument Buffer。

还有什么值得关注

  • 这个系列的前两场 Session 分别介绍了 Game Porting Toolkit 和 Metal Shader Converter,组合起来构成完整的移植路径。
  • Bindless 模式的详细说明可以参考”Go bindless with Metal 3”Session。
  • Apple GPU 的 TBDR 架构详解在”Bring your Metal app to Apple Silicon Macs”和”Harness Apple GPUs with Metal”Session 中。
  • MetalFX 的超分辨率技术对性能敏感的游戏场景(如 4K 渲染)提升明显。
  • Argument Buffer 在 Metal 3 中的效率有显著提升,对于从 Direct3D 12 或 Vulkan 移植的引擎来说是利好。
WWDC 2023