WWDC Quick Look 💓 By SwiftGGTeam
Build Metal-based Core Image kernels with Xcode

Build Metal-based Core Image kernels with Xcode

观看原视频

Highlight

Apple 为 Core Image kernel 提供 .ci.metal 构建流程:Xcode 用 -fcikernel-cikernel 在构建期生成 .ci.metallib,App 运行时用 CIColorKernel 加载并应用到 CIImage,减少运行时编译并获得 Metal 语法检查。

核心内容

写 Core Image 自定义滤镜时,开发者关心两件事:滤镜能不能进入 Core Image 的 automatic tiling 和 concatenation 管线,开发时能不能尽早发现 shader 语法错误。这个 session 的答案是把 CIKernel 写成 Metal Shading Language(Metal 着色语言),并让 Xcode 在 App 构建阶段完成 Core Image 专用的编译和链接。

这条路径把工作拆成五步。先给 target 增加 .ci.metal.ci.air 两条 build rule;再把 kernel 放进以 .ci.metal 结尾的源文件;接着按 Core Image 的函数签名写 Metal 函数;最后在 Swift 里加载 .ci.metallib,通过 CIColorKernel.apply 生成新的 CIImage

演示用的是 HDR zebra stripe 滤镜。它读取输入像素和目标坐标,在高于 SDR 白点的 HDR 区域画出红色斑马纹。这个例子很小,但覆盖了完整链路:.ci.metal 源文件、Core Image 提供的 Metal 类型、运行时加载、以及 CIFilter 子类的 outputImage

详细内容

1. 先把 .ci.metal 纳入 Xcode 构建(01:20

Core Image Metal code 不能直接走普通 Metal compute 或 graphics shader 的编译方式。session 建议在 target 里增加两条 custom build rule。

  • 第一条匹配 .ci.metal 文件,调用 Metal compiler,并带上 -fcikernel,产物是 .ci.air
  • 第二条匹配 .ci.air 文件,调用 Metal linker,并带上 -cikernel,产物放进 App resources,后缀是 .ci.metallib
  • 新建源文件时,文件名要以 .ci.metal 结尾,这样第一条 build rule 才会命中。

关键点:

  • -fcikernel-cikernel 来自 transcript 的构建步骤,不是普通 Metal shader 的默认参数。
  • 编译结果进入 App resources 后,Swift 代码才能用 Bundle.main.url(forResource:withExtension:) 找到它。
  • 这一步把 kernel 编译从运行时提前到构建期,同时保留 Xcode 的语法高亮和构建期语法检查。

2. 在 .ci.metal 文件里写 Core Image kernel(03:08

示例 kernel 来自 HDR 视频场景。它接收一个输入像素、一个时间参数和目标坐标,在高亮区域输出红色像素。

// MyKernels.ci.metal
#include <CoreImage/CoreImage.h> // includes CIKernelMetalLib.h
using namespace metal;

extern "C" float4 HDRZebra(coreimage::sample_t s, float time, coreimage::destination dest)
{
    float diagLine = dest.coord().x + dest.coord().y;
    float zebra = fract(diagLine / 20.0 + time * 2.0);
    if ((zebra > 0.5) && (s.r > 1 || s.g > 1 || s.b > 1))
        return float4(2.0, 0.0, 0.0, 1.0);
    return s;
}

关键点:

  • #include <CoreImage/CoreImage.h> 引入普通 Metal 类,也引入 Core Image 提供的 kernel 类型。
  • extern "C" 是 Core Image kernel 函数的声明要求。
  • coreimage::sample_t 表示输入图像中的一个像素;transcript 明确说明它是线性、预乘 alpha 的 RGBA float4,可用于 SDR 或 HDR 图像。
  • coreimage::destination 提供目标像素坐标,示例用 dest.coord().x + dest.coord().y 计算斜向条纹。
  • 条件 s.r > 1 || s.g > 1 || s.b > 1 对应“高于正常 SDR white 1”的 HDR 像素判断。

3. 用 CIColorKernel 加载 .ci.metallib04:58

Metal kernel 编译进 resources 后,Swift 侧通常把它包进 CIFilter 子类。session 建议把 CIKernel 对象放在 static property 里,让 metallib 的读取和初始化只发生一次。

class HDRZebraFilter: CIFilter {
    var inputImage: CIImage?
    var inputTime: Float = 0.0

    static var kernel: CIColorKernel = { () -> CIColorKernel in
        let url = Bundle.main.url(forResource: "MyKernels",
                                  withExtension: "ci.metallib")!
        let data = try! Data(contentsOf: url)
        return try! CIColorKernel(functionName: "HDRzebra",
                                  fromMetalLibraryData: data)
    }()

    override var outputImage: CIImage? {
        get {
            guard let input = inputImage else { return nil }
            return HDRZebraFilter.kernel.apply(extent: input.extent,
                                               arguments: [input, inputTime])
        }
    }
}

关键点:

  • Bundle.main.url(forResource: "MyKernels", withExtension: "ci.metallib") 对应前面 build rule 产出的资源文件。
  • Data(contentsOf:) 读取 metallib 二进制数据。
  • CIColorKernel(functionName:fromMetalLibraryData:) 从 Metal library 中取出指定 kernel。
  • static property 让加载和初始化只在第一次使用时发生,避免每帧重复读取资源。
  • outputImage 里先检查 inputImage,再用 apply(extent:arguments:) 创建新的 CIImage recipe。

4. 记住这条路径适合的收益(00:20

session 开头列出的收益很具体。Metal-based Core Image kernel 仍然可以使用 CIKernel 的 automatic tiling 和 concatenation;编译工作提前到 App 构建阶段,可以降低运行时编译开销;Metal 版本还能访问 gather reads、group writes、half-float math 这些高性能能力。

开发体验也更直接。.ci.metal 在 Xcode 中写,输入时有语法高亮,构建时有语法检查。对一个需要不断调整视觉效果的滤镜来说,错误越早暴露,迭代成本越低。

核心启发

  • 做什么:给 HDR 视频预览加一个过曝 zebra overlay。为什么值得做:session 的 HDRZebra kernel 已经展示了如何判断像素是否高于 SDR white 1,并用红色条纹标出区域。怎么开始:先把示例写进 MyKernels.ci.metal,再用 HDRZebraFilter 接收 inputImage 和动画时间参数。

  • 做什么:把图片编辑 App 的自定义滤镜迁移到 .ci.metal为什么值得做:构建期会执行 Core Image 专用的 Metal 编译和链接,语法错误能在 build 阶段暴露。怎么开始:给 target 加 .ci.metal.ci.air build rule,把滤镜函数声明为 extern "C",再用 CIColorKernel.ci.metallib 加载。

  • 做什么:给滤镜预览加一个实时速度控制。为什么值得做:示例中的 inputTime 会作为参数传给 kernel,apply(extent:arguments:) 支持把输入图像和时间值一起用于生成新的 CIImage怎么开始:在 CIFilter 子类上保留 inputTime,用滑块或播放时钟更新这个值,再把 [input, inputTime] 传入 arguments 数组。

  • 做什么:为视频工具建立“构建失败即停止”的 shader 校验流程。为什么值得做:session 明确把 Metal kernel 的 syntax checking 放到 build 时完成,适合在 CI 或本地构建中提前挡住错误。怎么开始:把所有 Core Image Metal 源文件统一命名为 .ci.metal,确保 target build rule 会生成 .ci.metallib,再让 App 启动时只加载构建产物。

关联 Session

评论

GitHub Issues · utterances