Metalの恩恵は受けつつCore Imageで「手軽に」画像処理


Metalでシェーダを書けば自由自在にGPU-acceleratedな画像処理を行えるわけですが、Core Imageの200種類近くある豊富なフィルタを利用しつつ、Metalの恩恵も受ける、という選択肢もあります。

既存のCore Imageのフィルタ(CIFilter)を利用することで、以下のようなメリットがあります。

  • 自分でイチから書かなくて良い
    • もちろんこっちの方が楽
    • 画像処理のプロが書いたであろうものを利用した方が大抵の場合良い結果が得られる
  • 種類がたくさんある
  • 簡単に使える
    • 数行書くだけ
    • どのフィルタも基本的な使い方は同じ
  • CIFilterはMetalとシームレスに統合されるようにつくられている(後述)
  • ビルトインフィルタの一部はMetal Performance Shadersを用いて実装されている

(Core ImageのCIPixellateフィルタをパラメータを変えつつ60fpsでMTKViewに描画。実装方法は後述)

MetalとCIFilterの連携について

「Metalとシームレスに統合」と上で書きましたが、それだけ言われてもなんのこっちゃ、という感じだと思います。

このテーマについてはWWDC2015の「What's New in Core Image」というセッションで詳しく解説されています。

上の図は、Core Imageのフィルタはコマンドバッファのどこにでも挿入できるよ、ということを言っています。

「MetalでリサイズしたテクスチャをCore Imageで画像処理してMetalで描画」みたいなことをしたいとして、そのためにCPUとGPUを行ったり来たりみたいなムダなことはしないので安心して併用してください、ということかと思います。

実装

MTLTextureにCore Imageで画像処理 → MTKViewに描画する、という実装例を示します。

MetalでGPUベースレンダリングするためのコンテキスト(CIContext)を生成しておきます。

private let device: MTLDevice
private let context: CIContext
device = MTLCreateSystemDefaultDevice()!
context = CIContext(mtlDevice: device)

MTLTextureから入力用のCIImageを生成します。

let inputImage = CIImage(mtlTexture: texture, options: nil)

何らかのCore Imageフィルタ(CIFilter)を生成し、入力画像とパラメータをセットします。

let filter = CIFilter(name: "CIPixellate")!
filter.setValue(inputImage, forKey: kCIInputImageKey)
filter.setValue(50.0, forKey: kCIInputScaleKey)

MTKViewdrawInView:メソッド内で、CIFilterの出力画像(outputImage)を描画します。

guard let outputImage = filter.outputImage else {return}
let commandBuffer = commandQueue.makeCommandBuffer()

let colorSpace = CGColorSpaceCreateDeviceRGB()
context.render(outputImage, to: drawable.texture, commandBuffer: commandBuffer, bounds: outputImage.extent, colorSpace: colorSpace)

commandBuffer.present(drawable)
commandBuffer.commit()
commandBuffer.waitUntilCompleted()

CIImageCAMetalDrawabletextureMTLTexture)に書き込むために、CIContextrender(_:to:commandBuffer:bounds:colorSpace:)を使用するのがポイントです。1

注意点

MTLTestureからCIImageを生成する際、画像が上下反転してしまいます。

というわけで実際にはy方向にフリップさせて使ってます。

let inputImage = CIImage(mtlTexture: texture, options: nil)?.applying(CGAffineTransform(scaleX: 1, y: -1).translatedBy(x: 0, y: CGFloat(texture.height)))

  1. MTKViewを用いて2Dテクスチャを表示する基本についてはこちらに書いてあります:MetalでUIImageViewライクに画像を表示する - 最小実装編 - Qiita