Bring your Metal app to Apple silicon Macs
Graphics & Games 进阶 30m

将 Metal 应用移植到 Apple Silicon Mac

Bring your Metal app to Apple silicon Macs

2020年6月24日

在 Apple 官方观看视频

一句话判断

Apple Silicon 的 GPU 和 Intel GPU 架构完全不同——如果你的 Metal 应用只测试过 Intel Mac,移植到 Apple Silicon 时一定会踩坑。这场 Session 告诉你坑在哪里。

这场 Session 讲了什么

这场 Session 是 Apple 宣布转向自研芯片后最重要的开发者指南之一。演讲者详细讲解了如何将现有的 Metal 应用从 Intel Mac(使用 Intel Iris 集显或 AMD 独显)移植到 Apple Silicon Mac(使用 Apple 自研 GPU)。

首先介绍了 Apple Silicon 与 Intel Mac 在 GPU 架构上的根本差异。Intel/AMD GPU 使用 Immediate Mode Rendering(IMR),Apple GPU 使用 Tile-Based Deferred Rendering(TBDR)。这意味着很多在 Intel/AMD 上”碰巧能用”的渲染技巧,在 Apple GPU 上可能表现完全不同。

Session 的核心部分列举了最常见的移植问题:存储模式(Storage Mode)的不兼容——Intel Mac 上的 .managed 模式在 Apple GPU 上需要改为 .shared.private;隐式的内存同步假设——TBDR 架构对内存可见性的时序与 IMR 不同;以及 Shader 编译的差异——Apple GPU 使用不同的指令集。

演讲者还讨论了 Universal Binary 的构建策略——如何在同一个 App Bundle 中同时包含 Intel 和 Apple Silicon 的 GPU 代码,以及如何使用 Rosetta 2 的 Metal 翻译层来运行尚未移植的应用。

值得深挖的点

  • Storage Mode 的迁移矩阵:Intel Mac 上 .managed 模式(CPU 和 GPU 各有一份拷贝,需要手动同步)在 Apple Silicon 上不存在——因为 CPU 和 GPU 共享统一内存。.managed 需要改为 .shared(CPU 和 GPU 直接访问同一块内存)。如果你在代码中硬编码了 .managed,在 Apple Silicon 上会回退到低效的模拟。
  • Rosetta 2 的 Metal 翻译限制:Rosetta 2 可以翻译 CPU 指令,但 Metal shader 无法实时翻译——它们需要在目标 GPU 上编译。Apple 的方案是在 App 首次启动时将所有 Metal shader 重新编译为 Apple GPU 指令。这可能导致首次启动非常慢。

代码片段

// 正确处理跨平台的 Storage Mode
func optimalStorageMode(for device: MTLDevice) -> MTLStorageMode {
    if device.hasUnifiedMemory {
        // Apple Silicon:统一内存架构
        return .shared
    } else {
        // Intel Mac:分离内存架构
        return .managed
    }
}

// 创建跨平台兼容的 Buffer
func createBuffer(
    device: MTLDevice,
    size: Int,
    options: MTLResourceOptions = []
) -> MTLBuffer {
    let storageMode = optimalStorageMode(for: device)
    return device.makeBuffer(
        length: size,
        options: MTLResourceOptions(rawValue: UInt(storageMode.rawValue))
    )!
}
// 跨平台的纹理创建
func createTexture(device: MTLDevice) -> MTLTexture {
    let descriptor = MTLTextureDescriptor()
    descriptor.width = 1024
    descriptor.height = 1024
    descriptor.pixelFormat = .rgba8Unorm

    if device.hasUnifiedMemory {
        // Apple Silicon:使用 shared 模式
        descriptor.storageMode = .shared
    } else {
        // Intel Mac:GPU 纹理使用 private 模式
        // 需要上传时用 managed buffer 中转
        descriptor.storageMode = .private
    }

    return device.makeTexture(descriptor: descriptor)!
}
// 检测设备特性做差异化处理
func configureForDevice(_ device: MTLDevice) {
    // 检测是否为 Apple GPU
    if device.supportsFamily(.apple1) {
        // Apple GPU 特定优化
        // 使用 memoryless render target
        // 使用 tile shader
        configureAppleGPUOptimizations(device)
    } else if device.supportsFamily(.mac1) {
        // Intel/AMD GPU 传统路径
        configureTraditionalGPUPath(device)
    }

    // 检测统一内存
    if device.hasUnifiedMemory {
        // 统一内存优化:CPU 和 GPU 可以共享指针
        setupSharedMemoryBuffers()
    }
}

最佳实践

  • 在 Xcode 中同时配置 Intel 和 Apple Silicon 两个 destination 进行测试
  • 将所有 .managed storage mode 替换为根据设备动态选择的逻辑
  • 利用 device.hasUnifiedMemory 和 GPU Family 检测做差异化代码路径
  • 构建时包含 Metal Binary Archive,减少 Apple Silicon 上的首次启动 shader 编译开销
  • 在 CI 中添加 Apple Silicon 测试节点,确保移植问题能被及早发现

还有什么值得关注

  • Apple Silicon 的统一内存架构不仅影响 GPU 代码,也影响 CPU 侧的内存管理策略
  • 搭配 “Optimize Metal Performance for Apple silicon Macs” 一起看,可以在移植完成后进一步优化性能
  • “Harness Apple GPUs with Metal” 讲解了 Apple GPU 架构的技术细节,是理解移植问题的理论基础
WWDC 2020