Highlight
Apple 建议视频 App 为每个视图复用一个 CIContext,关闭中间结果缓存,并在与 Metal 混用时把 CIContext 绑定到同一 MTLCommandQueue,以减少内存占用和 GPU 队列等待。
核心内容
视频滤镜和静态图片滤镜的压力不同。视频每一帧都在变化,App 如果频繁创建 CIContext,或者让 Core Image 和自己的 Metal 渲染各走一条命令队列,就会把时间花在初始化、缓存和等待同步上。画面越接近实时播放,这些开销越容易变成掉帧。
这场 session 把优化拆成三段。先把 CIContext 做成每个 view 一个长期对象,并关闭中间结果缓存;再把滤镜尽量放到 Metal 实现中,优先使用内建 Core Image filter,必要时编写 .ci.metal kernel;最后选择适合视频播放的 view,使用 AVMutableVideoComposition 交给 AVFoundation 播放,或用 MTKView 自己控制渲染和显示。
这些建议都围绕同一个目标:让 Core Image 的工作贴近视频帧的生命周期。每帧只保留必要状态,GPU 工作放在正确的队列里,最终输出直接进入视频播放或 Metal drawable。
详细内容
1. 为视频视图创建 CIContext(00:52)
Apple 的第一条建议是每个 view 只创建一个 CIContext。CIContext 容易创建,但初始化会消耗时间和内存。视频帧每次都不同,中间结果缓存的收益很低,关闭它能降低内存占用。
let context = CIContext(options: [
.cacheIntermediates: false,
.name: "MyAppView"
])
关键点:
.cacheIntermediates: false对应 transcript 中的视频场景:每帧内容不同,缓存中间图像会增加内存压力。.name给 context 一个可读名称,配合 Core Image 调试工具时更容易定位是哪一个 view 的管线。- 这个 context 应该跟 view 生命周期绑定,不要在每一帧重新创建。
如果 App 已经有自己的 Metal 渲染步骤,CIContext 应该使用同一条 Metal command queue。session 中的时间线示例说明,分开的队列会让 App 发出 wait 命令,GPU 管线中会出现空泡。
let context = CIContext(mtlCommandQueue: queue, options: [
.cacheIntermediates: false
])
关键点:
mtlCommandQueue让 Core Image 和其他 Metal render 共用同一条队列。- 输入或输出是 Metal texture 时,这种写法能减少跨队列同步。
- transcript 明确指出,共用队列可以移除等待,让工作更好地流水化。
2. 优先使用内建 Core Image filter(02:59)
session 建议优先使用内建 filter,因为这些 filter 已经由 Metal 实现。Xcode 文档也补充了参数说明、示例图片和示例代码。
import CoreImage.CIFilterBuiltins
func motionBlur(inputImage: CIImage) -> CIImage? {
let motionBlurFilter = CIFilter.motionBlur()
motionBlurFilter.inputImage = inputImage
motionBlurFilter.angle = 0
motionBlurFilter.radius = 20
return motionBlurFilter.outputImage
}
关键点:
CoreImage.CIFilterBuiltins提供类型化的 filter 入口,示例中直接创建CIFilter.motionBlur()。inputImage、angle、radius是这个滤镜的输入属性。- 返回
outputImage时还只是 Core Image recipe,真正渲染会在后面的播放或 view 输出阶段发生。
3. 把自定义 CIKernel 写进 .ci.metal(03:56)
当内建 filter 不够用时,session 建议使用 Metal 编写自定义 CIKernel。这样可以把编译工作移动到 App 构建阶段,并获得 gather reads、group writes、half-float math、语法高亮和构建期语法检查。
// 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 提供的附加类型。extern "C"是 kernel 函数声明要求。coreimage::sample_t表示输入图像中的像素,transcript 说明它是线性、预乘 alpha 的 RGBAfloat4,适合 SDR 和 HDR。coreimage::destination提供目标像素坐标,示例用dest.coord()生成斑马纹。- 条件
s.r > 1 || s.g > 1 || s.b > 1对应 HDR 像素高于 SDR 白点的判断。
4. 用 AVMutableVideoComposition 接入 AVPlayerView(05:50)
如果 App 主要目标是播放带滤镜的视频,AVPlayerView 是最简单的路径。关键对象是 AVMutableVideoComposition,它用 asset 和每帧回调创建。回调里设置 Core Image filter,然后把输出交回 request。
let videoComposition = AVMutableVideoComposition(
asset: asset,
applyingCIFiltersWithHandler: { request in
let filter = HDRZebraFilter()
filter.inputImage = request.sourceImage
if let output = filter.outputImage {
request.finish(with: output, context: myCtx)
} else {
request.finish(with: err)
}
}
)
关键点:
applyingCIFiltersWithHandler的 handler 会按视频帧调用。request.sourceImage是当前帧输入图像。request.finish(with:context:)把 Core Image 输出交回 AVFoundation。- transcript 提到 Xcode 调试时可以点开
CIImage的 eye 图标,查看组成该图像的 recipe;示例里还能看到 10-bit HDR surface 从 HLG 自动色彩管理到 Core Image working space。
5. 用 MTKView 控制渲染和显示(07:01)
需要更多控制权时,可以用 MTKView。初始化阶段创建 CIContext,允许 Core Image 使用 Metal Compute,并在 HDR view 上设置浮点像素格式和扩展动态范围。
class MyView: MTKView {
var context: CIContext
var commandQueue: MTLCommandQueue
override init(frame frameRect: CGRect, device: MTLDevice?) {
let dev = device ?? MTLCreateSystemDefaultDevice()!
context = CIContext(mtlDevice: dev, options: [.cacheIntermediates: false])
commandQueue = dev.makeCommandQueue()!
super.init(frame: frameRect, device: dev)
framebufferOnly = false
colorPixelFormat = MTLPixelFormat.rgba16Float
if let caml = layer as? CAMetalLayer {
caml.wantsExtendedDynamicRangeContent = true
}
}
}
关键点:
framebufferOnly = false让 Core Image 能使用 Metal Compute。rgba16Float对应 macOS HDR view 的设置建议。wantsExtendedDynamicRangeContent = true让CAMetalLayer支持扩展动态范围内容。
绘制时,session 使用 CIRenderDestination,传入返回 drawable texture 的 block。这样 CIContext 可以先开始排队 Metal work,再等待上一帧完成。
func draw(in view: MTKView) {
let size = self.convertToBacking(self.bounds.size)
let destination = CIRenderDestination(
width: Int(size.width),
height: Int(size.height),
pixelFormat: colorPixelFormat,
commandBuffer: nil
) {
return view.currentDrawable!.texture
}
context.startTask(toRender: image, from: rect, to: destination, at: point)
let commandBuffer = commandQueue.makeCommandBuffer()!
commandBuffer.present(view.currentDrawable!)
commandBuffer.commit()
}
关键点:
CIRenderDestination记录目标尺寸、像素格式和延迟获取 texture 的 block。startTask(toRender:from:to:at:)启动 Core Image 渲染任务。- 最后用 command buffer present 当前 drawable,完成本帧显示。
核心启发
- 实时 HDR 过曝提示:做一个视频预览叠加层,把高于 SDR 白点的区域标红。这个 session 的
HDRZebrakernel 已经给出判断方式;可以先用.ci.metal文件实现 kernel,再把它接入AVMutableVideoComposition。 - 低内存视频滤镜预览器:给剪辑 App 做一组实时滤镜预览。入口是每个 preview view 复用一个
CIContext,并设置.cacheIntermediates: false,让滑动时间线时不会积累无用中间帧。 - Core Image 与 Metal 混合渲染播放器:在已有 Metal overlay 的播放器里加入 Core Image 滤镜。关键是用同一个
MTLCommandQueue创建CIContext,让 Metal 输入、Core Image 处理和最终叠加保持在同一条队列上。 - 可视化滤镜调试模式:给内部调试版加一个按钮,打开当前帧的
CIImagerecipe 检查。session 展示了 Xcode 的 eye 图标调试入口,可以把这个流程写进性能排查手册。 - HDR MetalKit 预览窗口:为 macOS 视频工具做一个
MTKView预览。先设置framebufferOnly = false、rgba16Float和wantsExtendedDynamicRangeContent,再用CIRenderDestination把 Core Image 输出送到当前 drawable。
关联 Session
- Build Metal-based Core Image kernels with Xcode — 继续讲
.ci.metal文件、kernel 加载和 Core Image 自定义滤镜的集成方式。 - Discover Core Image debugging techniques — 讲 Core Image graph 的生成和解读,适合排查本 session 中的内存、颜色和性能问题。
- Edit and play back HDR video with AVFoundation — 展开 HDR 视频编辑与播放,并包含用 Core Image filter 创建
AVMutableVideoComposition的流程。 - Export HDR media in your app with AVFoundation — 补足 HDR 内容导出部分,适合把实时滤镜处理延伸到导出工作流。
评论
GitHub Issues · utterances