Build Metal-based Core Image kernels with Xcode
Graphics & Games 进阶 22m

在 Xcode 中构建基于 Metal 的 Core Image Kernel

Build Metal-based Core Image kernels with Xcode

2020年6月24日

在 Apple 官方观看视频

一句话判断

从 WWKernelLanguage 迁移到 Metal-based CIKernel 不只是换个语言——Xcode 的工具链支持让你可以在 IDE 内直接编写、编译和调试自定义 Core Image 滤镜,开发体验提升了一个量级。

这场 Session 讲了什么

Core Image 自定义滤镜过去使用一种叫 CIKL(Core Image Kernel Language)的类 GLSL 语言编写,需要运行时编译,调试困难。2020 年苹果推出了基于 Metal 的 CIKernel,可以直接在 Xcode 中编写和预编译。

Session 展示了三种 Metal-based Kernel:CIColorKernel(颜色变换,一对一)、CIWarpKernel(几何变形,改变采样坐标)、CIGeneralKernel(通用,可同时改变颜色和坐标)。每种 Kernel 都有对应的 Metal 函数签名和参数约定。

最大的改进是开发工具:Xcode 现在可以在构建时编译 CIKernel Metal 代码,提供语法高亮、错误提示和性能分析。你可以用 Metal Performance Shader 的调试工具来检查 Kernel 的执行状态。

值得深挖的点

从运行时编译到构建时编译。 CIKL 时代,Kernel 代码是字符串,运行时才编译,写错一个变量名要等到运行时才能发现。Metal-based Kernel 在构建时编译,类型安全、错误提示、代码补全一应俱全。这对大型项目的 CI 管线也更友好。

三种 Kernel 的性能差异。 CIColorKernel 只处理颜色不改变坐标,CIWarpKernel 只改变采样坐标不做颜色计算——这两种专用 Kernel 可以被 Core Image 优化合并到更大的渲染管线中。CIGeneralKernel 灵活性最高但优化空间最小。

代码片段

// Metal-based CIColorKernel
// 输入一个像素颜色,输出变换后的颜色
#include <CoreImage/CoreImage.h>

extern "C" float4 vintageFilter(
    coreimage::sample_t inputImage,
    float intensity,
    coreimage::destination dest
) {
    float4 color = inputImage;
    
    // 褐色调映射
    float3 vintage = float3(
        dot(color.rgb, float3(0.393, 0.769, 0.189)),
        dot(color.rgb, float3(0.349, 0.686, 0.168)),
        dot(color.rgb, float3(0.272, 0.534, 0.131))
    );
    
    // 按强度混合原图和怀旧色
    float3 result = mix(color.rgb, vintage, intensity);
    return float4(result, color.a);
}
// Metal-based CIWarpKernel
// 输入坐标,返回新的采样坐标
extern "C" float2 rippleWarp(
    float2 coord,
    float amplitude,
    float frequency,
    coreimage::destination dest
) {
    // 以图像中心为原点计算涟漪偏移
    float2 center = float2(dest.size() * 0.5);
    float2 delta = coord - center;
    float distance = length(delta);
    
    // 正弦波扭曲
    float offset = amplitude * sin(distance * frequency);
    return coord + normalize(delta) * offset;
}
// Swift 中注册和使用 Metal-based CIKernel
let kernelURL = Bundle.main.url(
    forResource: "CustomFilters",
    withExtension: "ci.metallib"  // Xcode 自动编译的产物
)!
let kernel = try CIKernel.kernels(
    fromMetallibInLibrary: try .init(url: kernelURL),
    withMaximumWorkingImageSize: CGSize(width: 4096, height: 4096)
)

// 创建自定义 CIFilter
class VintageFilter: CIFilter {
    var inputImage: CIImage?
    var inputIntensity: CGFloat = 0.8
    
    override var outputImage: CIImage? {
        guard let input = inputImage else { return nil }
        return kernel.apply(
            extent: input.extent,
            arguments: [input, inputIntensity]
        )
    }
}

最佳实践

  • 优先使用专用的 Kernel 类型。 能用 CIColorKernel 解决的不要用 CIGeneralKernel,专用类型有更多优化空间。
  • 利用 Xcode 的实时预览。 Metal-based Kernel 支持在 Xcode 的 Playground 中实时预览效果,方便迭代。
  • 注意 Kernel 的执行区域(Extent)。 Warp Kernel 可能会读取输入图像范围外的像素,需要正确声明 ropingRegion 让 Core Image 知道需要多少额外数据。
  • 将多个自定义 Kernel 串联使用。 Core Image 会自动合并同一管线中的多个 Kernel,减少中间缓冲区。

还有什么值得关注

  • Xcode 的 Metal 调试工具现在支持 Core Image Kernel 的单步调试
  • CIKernel 支持在函数内部使用 Metal 的数学函数库
  • 性能上 Metal-based Kernel 与 CIKL Kernel 基本一致,优势主要在开发体验
  • 可以将现有的 CIKL 代码逐步迁移到 Metal,两种 Kernel 可以在同一管线中混用
WWDC 2020