目次
概要
Core Imageの処理能力を活用し、App内の動画パフォーマンスを最適化する方法についてAppで動画にエフェクトを適用するためのCore Imageパイプラインを構築する方法について
CIContextを使用するときに、Appのメモリフットプリントを減らす方法、及び
Core Imageフィルタを使用した動画再生のためのAVPlayerViewまたはMTKView classの使用に関するベストプラクティスについて
さらに、独自のカスタムカーネルをMetal Shading Languageで書くべき理由について説明し、 Core ImageパイプラインでのMetalコマンドキューを最適に使用するためのパフォーマンスに関するヒントを紹介
CIContextの生成方法
- CIContextの生成は1つのViewに対して1つのCIContextのみ生成する
- 初期化に時間とメモリ容量を要するため、頻繁には生成を行わないようにする
- 最も重要なのは中間生成物をキャッシュに格納しないこと
- 動画は各フレームが前のフレームとは異なるため、キャッシュの無効化によりメモリ使用量を抑制します
- Core Imageのデバッグ技術の使用時に有効であるため、contextに名前をつけること
let context = CIContext(options: [
.cacheIntermediates : false,
.name : ”MyAppView”
])
- Core Imageと他のMetal API群を混在して使用する場合(MetalTextureをCore Imageの入力値 or 出力値で使用)は、Core ImageをMetalCommandQueueで生成する
let context = CIContext(MTLCommandQueue : queue, options: […])
- 全ての処理が異なるキューで行われるため、正しい結果を得るにはwaitコマンドが必要
- 非効率なパイプライン処理による無駄な時間を省くため、他のMetalレンダリング処理で使うキューと同じキューでCIContextを生成します
- これにより待ちを減らし最適なパフォーマンスを発揮するパイプライン処理が可能

カスタムCIKernelの書き方と適用方法
- 可能な限りMetalで最適化されたビルドインのCIFilterを使用する
- サンプルコード
- CIKernelの通常機能である自動タイリングや自動連結だけでなく、Metalで書くことによりコンパイル処理時間を減らすことが可能
- ギャザー読み取り、グループ書き込み、半精度浮動小数点数などの機能も利用可能
- Writing Custom Kernels

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.hのヘッダーをinclude
- kernerlのための関数
extern C
を宣言
// 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;
}
動画アプリの開発におけるViewへの最適なレンダリング方法について
- アプリに動画エフェクト処理がある場合、UIImageView, NSImageViewの使用は避けること
- 静的コンテンツ表示のために設計されているから
- AVPlayerViewを使用してViewにfilter処理した動画を自動的に映す
- パフォーマンス向上を目的として、MetalKitView(MTKView)も選択可能
AVPlayerViewを使用したfilterの実装
AVMutableVideoComposition
ブロックは毎フレーム毎にコールされAVAsynchronousCIImageFilteringRequest
に渡される
let videoComposition = AVMutableVideoComposition(
asset: asset,
applyingCIFiltersWithHandler:
{ (request: AVAsynchronousCIImageFilteringRequest) -> Void in
// CIFilterの生成と入力値の設定、及び出力画像の取得
let filter = HDRZebraFilter()
filter.inputImage = request.sourceImage
// Xcodeではデバッグ中にCIImageにマウスオーバーするとオブジェクトのアドレスが表示され、
// 「目」のアイコンをクリックすると画像を構成するレシピが表示される
let output = filter.outputImage
if (output != nil) {
// リクエストオブジェクトに出力した画像を設定
request.finish(with: output, context: myCtx)
}
else { request.finish(with: err) }
}
)

MTKViewを使用したfilterの実装
class MyView : MTKView {
var context: CIContext
var commandQueue : MTLCommandQueue
override init(frame frameRect: CGRect, device: MTLDevice?) {
let dev = device ?? MTLCreateSystemDefaultDevice()!
// initメソッド内でCIContextを生成する
context = CIContext(mtlDevice: dev, options: [.cacheIntermediates : false] )
commandQueue = dev.makeCommandQueue()!
super.init(frame: frameRect, device: dev)
// Core ImageがMetal演算を使用出来るように frameBufferOnlyはfalseにする
framebufferOnly = false // allow Core Image to use Metal Compute
colorPixelFormat = MTLPixelFormat.rgba16Float
if let caml = layer as? CAMetalLayer {
caml.wantsExtendedDynamicRangeContent = true
}
}
func draw(in view: MTKView) {
let size = self.convertToBacking(self.bounds.size)
// Metal textureを渡す代わりにtextureを返すブロックを渡している
// CIContextは前のフレーム処理が終わる前に、Metal処理のキュー追加が可能となる
let rd = CIRenderDestination(width: Int(size.width),
height: Int(size.height),
pixelFormat: colorPixelFormat,
commandBuffer: nil)
{ () -> MTLTexture in return view.currentDrawable!.texture }
// CIContextにdestinationへの画像レンダリングタスクを指示
context.startTask(toRender:image, from:rect, to:rd, at:point)
// Present the current drawable
// 現在描画可能なViewを示すためのコマンドバッファを生成する
let cmdBuf = commandQueue.makeCommandBuffer()!
cmdBuf.present(view.currentDrawable!)
cmdBuf.commit()
}