在 Xcode 中构建基于 Metal 的 Core Image Kernel
Build Metal-based Core Image kernels with Xcode
2020年6月24日
一句话判断
从 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 可以在同一管线中混用