Metal 使用教程:在 macOS 上编写并运行第一个 GPU 程序
Metal 是苹果提供的底层图形与通用计算接口,只能在 macOS 和 iOS 设备上运行。
如果你搜索 Metal 使用教程,真正要解决的问题通常是:我的设备能不能跑、需要装什么、第一个能跑起来的程序怎么写、跑不通时看哪里。
下面按这个顺序展开。
先确认设备和工具链是否就绪
Metal 依赖苹果 GPU 和系统自带的 Metal 框架,不需要单独下载 SDK。
动手前检查三件事:
- 系统版本:打开「关于本机」,确认 macOS 版本。较老的 Intel Mac 可能不支持某些 Metal 特性,Apple Silicon 机型默认支持。
- 开发工具:终端执行
xcode-select -p,如果提示未安装,执行xcode-select --install安装命令行工具。 - 编译器:执行
xcrun -sdk macosx metal --version,能输出版本号说明 Metal 编译器可用。
如果只是写命令行程序验证计算功能,不需要完整 Xcode 工程,用 swiftc 配合 metal 编译即可。
编写并编译一个计算着色器
Metal 的 GPU 代码写在 .metal 文件里,用 Metal Shading Language 编写。
新建 add.metal:
#include
using namespace metal;
kernel void add_arrays(device const float* inA,
device const float* inB,
device float* result,
uint index [[thread_position_in_grid]]) {
result[index] = inA[index] + inB[index];
}
这个内核把两个数组逐元素相加。
用命令行编译成 .metallib:
xcrun -sdk macosx metal -c add.metal -o add.air
xcrun -sdk macosx metallib add.air -o add.metallib
两条命令分别完成前端编译和链接。
如果第一条就报语法错误,先检查 #include 和 using namespace 是否写全。
在 Swift 中加载着色器并执行
创建 main.swift,核心步骤是获取设备、加载函数、建管线、建缓冲区、编码命令、等待完成。
import Metal
let device = MTLCreateSystemDefaultDevice()!
let library = try! device.makeLibrary(URL: URL(fileURLWithPath: "add.metallib"))
let function = library.makeFunction(name: "add_arrays")!
let pipeline = try! device.makeComputePipelineState(function: function)
let queue = device.makeCommandQueue()!
let count = 1024
let byteCount = count * MemoryLayout.stride
let a = device.makeBuffer(length: byteCount)!
let b = device.makeBuffer(length: byteCount)!
let out = device.makeBuffer(length: byteCount)!
let cmd = queue.makeCommandBuffer()!
let encoder = cmd.makeComputeCommandEncoder()!
encoder.setComputePipelineState(pipeline)
encoder.setBuffer(a, offset: 0, index: 0)
encoder.setBuffer(b, offset: 0, index: 1)
encoder.setBuffer(out, offset: 0, index: 2)
let threadsPerGroup = MTLSize(width: 64, height: 1, depth: 1)
let groups = MTLSize(width: (count + 63) / 64, height: 1, depth: 1)
encoder.dispatchThreadgroups(groups, threadsPerThreadgroup: threadsPerGroup)
encoder.endEncoding()
cmd.commit()
cmd.waitUntilCompleted()
编译运行:
swiftc main.swift -o run
./run
如果程序正常退出且没有崩溃,说明管线创建和命令提交成功。
要验证计算结果,可以把 out 的内容拷回 CPU 比较。
容易踩的几个坑
MTLCreateSystemDefaultDevice()返回 nil:说明当前环境没有可用 Metal 设备,常见于虚拟机或过旧硬件,先换真机测试。- 找不到函数名:
makeFunction(name:)里的名字必须和.metal中kernel后的函数名完全一致,大小写敏感。 .metallib路径错误:命令行程序的工作目录影响相对路径,最好用绝对路径加载。- 线程组尺寸超限:
threadsPerThreadgroup不能超过管线对象的maxTotalThreadsPerThreadgroup,否则编码阶段会报错。
怎么确认真的跑对了
最直接的验证是读取输出缓冲区:把 out.contents() 转成 UnsafePointer,打印前几个元素,和 CPU 端手算结果对照。
如果数值一致,说明从着色器编译到 GPU 执行的链路是通的。
另外可以在 Xcode 中给 MTLCommandBuffer 加完成回调,捕获 error 字段,比只看进程是否崩溃更容易定位问题。
Metal 使用教程的关键不在语法,而在设备、编译、加载、执行这四步是否各自成功。
先让最小内核跑通,再逐步加入纹理或更复杂的缓冲区绑定。