探索 Metal 4
Discover Metal 4
2025年6月10日
一句话判断
Metal 4 真正的卖点不是某个新 API,而是它让图形渲染和 ML 推理第一次在 GPU 命令层面上成为同一件事——如果你做游戏或者实时图像处理,这意味着你可以把 Neural Engine 和 GPU 当成一个统一的计算池来调度。
这场 Session 讲了什么
Metal 4 的命令编码模型做了一件很务实的事:把以前逐条验证、逐条提交的渲染命令改成批量处理,部分验证延迟到 GPU 实际执行时才做。这听起来像是底层实现细节,但对高帧率游戏来说,每帧省下的几毫秒 CPU 时间就是多出来的游戏逻辑预算。一个 60fps 的游戏每帧只有 16.7ms,如果命令编码吃掉 5ms,省下来就意味着帧时间多了 30% 的余量。
资源管理的思路也变了——不是”怎么拷贝得更快”,而是”怎么少拷贝”。Metal 4 让 CPU 和 GPU 在更多场景下直接共享同一块内存,省掉了中间的暂存缓冲区环节。这对动态顶点数据、每帧更新的 uniform buffer、流式加载的大纹理这些场景影响最直接。在 iPhone 和 iPad 这种内存吃紧的设备上,少一份拷贝就是少一份内存占用。
最值得关注的是 ML 操作在 GPU 侧的原生化。以前要在 GPU 上跑推理,得手动用 MPS(Metal Performance Shaders)搭计算管线,和渲染管线的协调全靠自己。Metal 4 把 ML 操作提到了一等公民的位置,渲染命令和推理命令可以混在同一个 command buffer 里提交,GPU 调度器自动排执行顺序。再配合 Neural Engine,你可以把卷积层丢给 GPU、全连接层丢给 ANE,搭一个混合推理管线。
值得深挖的点
命令编码的”惰性验证”到底省了什么
传统 Metal 的命令编码流程是:CPU 端编码一条命令,立刻做状态验证(管线状态是否匹配、顶点格式是否正确、资源绑定是否合法),验证通过才写入 command buffer。这意味着每个 draw call 都有一次同步的验证开销。Metal 4 的惰性验证(lazy validation)把一部分验证推迟到 GPU 实际消费命令时才做——CPU 端只做最基本的格式检查,深度验证交给 GPU 的硬件单元在执行阶段完成。
这个 trade-off 很明确:你用 CPU 端的快速失败能力换取了编码吞吐量。在旧模型下,如果某个 draw call 的状态有错,CPU 端立刻就能告诉你;新模型下,这个错误可能要到 GPU 执行时才暴露,调试时的错误定位会更难。但实际开发中,渲染管线一旦跑通,状态错误基本不会再出现,所以这个 trade-off 在生产环境中是合理的。真正的收益在于:当场景有几千个 draw call 时,CPU 编码阶段的耗时能显著缩短,留出更多时间给游戏逻辑、物理模拟和 AI 决策。
GPU ML 推理与 Core ML 的分工边界
Metal 4 原生支持 GPU 上的 ML 操作,但这不意味着 Core ML 过时了。两者的分工边界在于:Core ML 是一个自动选择最优硬件(CPU/GPU/ANE)的高层抽象,你丢一个模型进去,它帮你决定在哪跑;Metal GPU 推理给你的是精确控制——你决定每一层在哪跑、数据怎么流、和渲染管线怎么交织。
选 Metal GPU 推理的场景:需要和渲染管线深度耦合(比如实时风格迁移,渲染完直接在 GPU 上跑推理,结果写回纹理继续渲染)、需要自定义算子(Core ML 不支持的自定义层)、需要亚毫秒级延迟(Core ML 的调度开销在某些极端场景下不可接受)。选 Core ML 的场景:模型是独立的、不需要和渲染管线交互、你不想关心硬件细节。简单说,如果你的 ML 推理是渲染管线的一部分,用 Metal;如果是一个独立功能,用 Core ML。
代码片段
场景一:MetalFX 时间放大,720p 渲染输出 1440p
用低分辨率渲染再放大,是提升帧率最省事的办法。MetalFX 的时间放大器(temporal scaler)利用运动向量和历史帧信息做放大,画质比简单的双线性插值好得多。
import MetalFX
let desc = MTLFXTemporalScalerDescriptor()
desc.inputWidth = 1280
desc.inputHeight = 720
desc.outputWidth = 2560
desc.outputHeight = 1440
desc.colorTextureFormat = .bgra8Unorm
desc.depthTextureFormat = .depth32Float
desc.motionTextureFormat = .rg16Float
let scaler = desc.makeTemporalScaler(device: device)!
// 渲染循环末尾调用
scaler.encode(
commandBuffer: commandBuffer,
colorTexture: lowResColor,
depthTexture: depth,
motionTexture: motionVectors,
outputTexture: highResOutput
)
坑:运动向量的质量直接决定放大效果。半透明物体和粒子的运动向量处理不好,画面会拖影和闪烁。
场景二:同一个 command buffer 里混合渲染和 ML 推理
Metal 4 允许在同一个 command buffer 里交替编码渲染命令和计算命令,GPU 调度器自动排序。
let commandBuffer = commandQueue.makeCommandBuffer()!
// 先渲染一帧
let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: rpd)!
renderEncoder.setRenderPipelineState(pipeline)
renderEncoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 3)
renderEncoder.endEncoding()
// 紧接着在同一个 command buffer 里跑 ML 推理
let computeEncoder = commandBuffer.makeComputeCommandEncoder()!
computeEncoder.setComputePipelineState(mlPipeline)
computeEncoder.setBuffer(inputBuffer, offset: 0, index: 0)
computeEncoder.setBuffer(outputBuffer, offset: 0, index: 1)
computeEncoder.dispatchThreads(gridSize, threadsPerThreadgroup: threadGroupSize)
computeEncoder.endEncoding()
commandBuffer.commit()
坑:ML 推理和渲染共享 GPU 时间,推理太重会直接影响帧率。建议用单独的 command buffer 异步提交推理任务,或者把推理拆到前一帧的空闲时间里。
场景三:惰性同步替代显式 didModifyRange
频繁 CPU 写入的缓冲区可以用新的惰性同步 API,省掉手动标记修改范围的麻烦。
// 旧写法:每次修改后要手动通知
buffer.didModifyRange(range)
// Metal 4:直接写,GPU 读取时自动同步
let ptr = buffer.contents().bindMemory(to: Uniforms.self, capacity: 1)
ptr.pointee.modelViewProjection = mvpMatrix
// 不需要 didModifyRange,Metal 4 自动处理同步
坑:惰性同步依赖 Metal 4 的运行时,在旧设备上 API 会静默回退到 Metal 3 行为,别忘了在多设备上测试。
最佳实践
优先把 MetalFX 时间放大加上——这是投入产出比最高的改动,改几行代码就能在中低端设备上拿到可观的帧率提升,从 2x 放大倍率开始测,找到目标设备的甜点。
命令编码的优化不需要急着做。Metal 3 的代码在 Metal 4 上不会报错,新编码模型是增量加的,等有明确的 CPU 编码瓶颈时再迁移也不迟。先用 Instruments 的 Metal System Trace 看看当前帧的 CPU 时间花在哪,如果命令编码占比超过 30%,再考虑迁移。
GPU ML 推理那条路,除非有明确的实时推理需求(比如实时风格迁移、实时超分),否则别急着上。Core ML 在大多数场景下够用,而且省心。Metal GPU 推理的优势在于和渲染管线的耦合能力,如果推理是独立的,没必要自己造轮子。
还有一点:Metal 4 的部分特性依赖新硬件,在旧设备上会回退。上线前务必在 A14 以下的设备上跑一遍,确认回退行为符合预期。
还有什么值得关注
- 着色器编译管线有优化,PSO 创建速度更快,应用冷启动和场景切换会更流畅,但具体提升幅度取决于你的着色器复杂度。
- 智能资源管理在
storageModeShared模式下自动生效,不需要改代码,但如果你用的是storageModePrivate,收益就小很多。 - MetalFX 空间放大(spatial scaling)比时间放大轻量,适合对画质要求不那么高但对性能极度敏感的场景,比如 VR 渲染。