iOS
画像処理
macos
Metal
Swift
MetalDay 5

Core ImageのカスタムフィルタをMetalで書く

Core Imageは以前から一部の下回りはMetalになっているという話はありましたが、iOS 11からはMetalシェーダ(Metal Shader Language)でカーネルを書けるようになりました。

wwdc-coreimage1.jpg

なお、A8以降のハードウェアで利用可能です。

従来の方法との比較

これまではCore Image Kernel Languageという専用の言語か、OpenGL Shading Language(GLSL)のサブセットを利用することができました。このあたりの仕様は以下のドキュメントで確認できます。

The Core Image kernel language defines functions, data types, and keywords that you can use to specify image processing operations for custom Core Image filters that you write. You can also use a subset of the OpenGL Shading Language (glslang).

This document defines the symbols in the Core Image kernel language and lists the symbols in the OpenGL Shading Language that are unsupported in Core Image filters.

GLSLやCIKernel Languageは利用時にコンパイルされるのですが、Metal Shader Language(以下MSL)はビルド時にコンパイルされるため、パフォーマンス面で有利です。

また今後iOSではGPUレイヤで処理をするにあたっては基本的にMetalを用いることになると思われるので、単純に使用言語を一本化できるというのもメリットのひとつかと思います。

(従来法について書いた記事)

実装

iOS 11で、CIKernelに次のようにMetalライブラリの関数から初期化するメソッドが追加されました。

CIKernel
public convenience init(functionName name: String, fromMetalLibraryData data: Data) throws
CIKernel
public convenience init(functionName name: String, fromMetalLibraryData data: Data, outputPixelFormat format: CIFormat) throws

次のようにMSLでカーネルを書いて、

kernel.metal
#include <metal_stdlib>
using namespace metal;
#include <CoreImage/CoreImage.h> // includes CIKernelMetalLib.h

extern "C" { namespace coreimage {

    float4 myColor(sample_t s) {

        return s.grba;
    }

}}

(色の順序を入れ替えているだけのシンプルなカーネル)

CIKernel(ここではそのサブクラスであるCIColorKernel)を初期化します1

MetalFilter.swift
let url = Bundle.main.url(forResource: "default", withExtension: "metallib")!
let data = try! Data(contentsOf: url)
let kernel = try! CIColorKernel(functionName: "myColor", fromMetalLibraryData: data)

カスタムフィルタクラスの実装の全体はこんな感じです。

MetalFilter.swift
import CoreImage

class MetalFilter: CIFilter {

    private let kernel: CIColorKernel

    var inputImage: CIImage?

    override init() {
        let url = Bundle.main.url(forResource: "default", withExtension: "metallib")!
        let data = try! Data(contentsOf: url)
        kernel = try! CIColorKernel(functionName: "myColor", fromMetalLibraryData: data)
        super.init()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func outputImage() -> CIImage? {
        guard let inputImage = inputImage else {return nil}
        return kernel.apply(extent: inputImage.extent, arguments: [inputImage])
    }
}

呼び出し側の実装:

let filter = MetalFilter()
filter.inputImage = inputImage
let outputImage = filter.outputImage()

ビルド設定

さて、上述の実装でビルドはできるのですが、実行してみると次のようなエラーにぶちあたります。

[api] +[CIKernel kernelWithFunctionName:fromMetalLibraryData:outputPixelFormat:error:] Function 'myColor' does not exist.

"myColor"がない、ということで、こういうときのデバッグ方法としてはMTLLibraryの関数名一覧を見てみるのですが、

let library = try! MTLCreateSystemDefaultDevice()!.makeLibrary(URL: url)
print("functions:\(library.functionNames)")

結果としては

functions:[]

と、何も見つかりません。どうやらnamespace coreimageのスコープ内で書いた関数はMTLLibraryでは扱われないようです。

調べていると、リファレンスに書いてありました。

ビルド設定で以下の2つが必要でした。

  • [Other Metal Compiler Flags]-fcikernelを指定する
  • User-Defined SettingとしてMTLLINKER_FLAGSキーを追加し、値に-cikernelを指定する

15241eb8-8ec6-4d66-a6ad-5d97105475ab.png

実行結果

(左:元画像 右:カスタムフィルタ適用後の画像)

関連記事

Metalの参考書籍

技術書「iOS 11 Programming」の、「第13章 Metal」を執筆しました。

書籍のタイトルにはiOS 11とありますが、Metalについては新機能の紹介だけではなくて、基礎からじっくり解説しています。Metalの章だけで37ページもあり、日本語ではこれだけのまとまったMetalの解説はレアかと思います。

  • 13.1 はじめに
  • 13.2 Metalの概要
  • 13.3 Metalの基礎
  • 13.4 MetalKit
  • 13.5 Metal入門その1 - 画像を描画する
  • 13.6 Metal入門その2 - シェーダを利用する
  • 13.7 Metal入門その3 - シェーダでテクスチャを描画する
  • 13.8 ARKit+Metalその1 - マテリアルをMetalで描画する
  • 13.9 ARKit+Metalその2 - MetalによるARKitのカスタムレンダリング
  • 13.10 Metal 2
  • 13.11 Metalを動作させるためのハードウェア要件

他の章も他著やネットではなかなか得られない濃い情報が詰まっているので、気になった方はぜひサンプルPDFもあるので見てみてください。

PEAKSのサイトにて電子書籍・紙の書籍ともに販売されています。

iOS 11 Programming

iOS 11 Programming

  • 著者:堤 修一,吉田 悠一,池田 翔,坂田 晃一,加藤 尋樹,川邉 雄介,岸川克己,所 友太,永野 哲久,加藤 寛人,
  • 発行日:2017年11月16日
  • 対応フォーマット:製本版,PDF
  • PEAKSで購入する

執筆を担当したARKit、Metalの章の詳細、あるいは全体的なおすすめポイントは以下の記事にも書きました。


  1. default.metallibについては本記事の終わりで紹介している「iOS 11 Programming」のMetalの章で解説しています。