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

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

![wwdc-coreimage1.jpg](https://qiita-image-store.s3.amazonaws.com/0/3180/d2156526-3352-cb7d-1ce2-75c16b78939f.jpeg)

- [Advances in Core Image: Filters, Metal, Vision, and More - WWDC 2017 - Videos - Apple Developer](https://developer.apple.com/videos/play/wwdc2017/510/)

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



## 従来の方法との比較

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

- [Core Image Kernel Language Reference](https://developer.apple.com/library/content/documentation/GraphicsImaging/Reference/CIKernelLangRef/Introduction/Introduction.html#//apple_ref/doc/uid/TP40004397)

> 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を用いることになると思われるので、単純に使用言語を一本化できるというのもメリットのひとつかと思います。

（従来法について書いた記事）

- [CIKernel を使用してCore Imageのカスタムフィルタをつくる - Qiita](https://qiita.com/shu223/items/6959dc7ba6ac0844340b)


## 実装

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

```swift:CIKernel
public convenience init(functionName name: String, fromMetalLibraryData data: Data) throws
```

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

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

```cpp: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）を初期化します[^metallib]。

```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)
```

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

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

```swift: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])
    }
}
```


呼び出し側の実装：

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

## ビルド設定

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

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

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

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

結果としては

> functions:[]

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

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

- [kernelWithFunctionName:fromMetalLibraryData:error: - CIKernel | Apple Developer Documentation](https://developer.apple.com/documentation/coreimage/cikernel/2880194-kernelwithfunctionname)

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

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


![15241eb8-8ec6-4d66-a6ad-5d97105475ab.png](https://qiita-image-store.s3.amazonaws.com/0/3180/df2bb459-9b36-38f1-0630-e2b134a9bf41.png)

## 実行結果

![](https://camo.qiitausercontent.com/51e5c1b162005a365c71d88c70c8d20f62c14e4f/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f333138302f38383761663939362d613364362d353331352d383764642d3632306230313466346435372e6a706567)

（左：元画像 右：カスタムフィルタ適用後の画像）


## 関連記事

- [Metalの恩恵は受けつつCore Imageで「手軽に」画像処理 - Qiita](https://qiita.com/shu223/items/4c0f8073477f787e1627)

<img src="https://camo.qiitausercontent.com/3bb22bf0f259ab564cf5be6d94ec933723464c61/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f333138302f66356461376639302d613266662d356264362d613738342d3962383030346266656539662e676966" width="200">

- [Core Imageを用いたリッチな画面遷移アニメーション - Over&Out その後](http://d.hatena.ne.jp/shu223/20151008/1444259843)

<img src="http://cdn-ak.f.st-hatena.com/images/fotolife/s/shu223/20151008/20151008085257.gif" width="200">


## Metalの参考書籍

技術書「[iOS 11 Programming](http://peaks.cc/shu223/iOS11)」の、「第13章 Metal」を執筆しました。

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

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

他の章も他著やネットではなかなか得られない濃い情報が詰まっているので、気になった方はぜひ[サンプルPDF](http://peaks.cc/shu223/iOS11)もあるので見てみてください。

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

<div class="peaks_widget" style="overflow:hidden; padding:20px; border:1px solid #dedede;"><div class="peaks_widget__image" style="float:left; margin-right:15px; line-height:0;"><a target="_blank" id="purchase" href="https://peaks.cc/shu223/iOS11"><img alt="iOS 11 Programming" style="border:none; max-width:140px;" src="https://s3-ap-northeast-1.amazonaws.com/peaks-images/project002_cover.jpg" width="500"></a></div><div class="peaks_widget__info"><p style="margin:0 0 3px 0; font-size:110%; font-weight:bold;"><a target="_blank" id="purchase" href="http://peaks.cc/shu223/iOS11">iOS 11 Programming</a></p><ul style="margin:0; padding:0;"><li style="font-size:90%; list-style:none;"><span>著者：</span><span>堤 修一,</span><span>吉田 悠一,</span><span>池田 翔,</span><span>坂田 晃一,</span><span>加藤 尋樹,</span><span>川邉 雄介,</span><span>岸川克己,</span><span>所 友太,</span><span>永野 哲久,</span><span>加藤&#160;寛人,</span></li><li style="font-size:90%; list-style:none;">発行日：2017年11月16日</li><li style="font-size:90%; list-style:none;">対応フォーマット：製本版,PDF</li><li style="font-size:90%; list-style:none;"><a target="_blank" id="purchase" style="text-decoration:underline; color:#1DA1F2;" href="http://peaks.cc/shu223/iOS11">PEAKSで購入する</a></li></ul></div></div>

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

- [「iOS 11 Programming」を共著で執筆しました／本書のおすすめポイント - Over&Out その後](http://d.hatena.ne.jp/shu223/20171128/1511827476)