Core Imageは以前から一部の下回りはMetalになっているという話はありましたが、iOS 11からはMetalシェーダ(Metal Shader Language)でカーネルを書けるようになりました。
なお、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ライブラリの関数から初期化するメソッドが追加されました。
public convenience init(functionName name: String, fromMetalLibraryData data: Data) throws
public convenience init(functionName name: String, fromMetalLibraryData data: Data, outputPixelFormat format: CIFormat) throws
次のようにMSLでカーネルを書いて、
#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。
let url = Bundle.main.url(forResource: "default", withExtension: "metallib")!
let data = try! Data(contentsOf: url)
let kernel = try! CIColorKernel(functionName: "myColor", fromMetalLibraryData: data)
カスタムフィルタクラスの実装の全体はこんな感じです。
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
を指定する
実行結果
(左:元画像 右:カスタムフィルタ適用後の画像)
関連記事
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のサイトにて電子書籍・紙の書籍ともに販売されています。
- 著者:堤 修一,吉田 悠一,池田 翔,坂田 晃一,加藤 尋樹,川邉 雄介,岸川克己,所 友太,永野 哲久,加藤 寛人,
- 発行日:2017年11月16日
- 対応フォーマット:製本版,PDF
- PEAKSで購入する
執筆を担当したARKit、Metalの章の詳細、あるいは全体的なおすすめポイントは以下の記事にも書きました。
-
default.metallib
については本記事の終わりで紹介している「iOS 11 Programming」のMetalの章で解説しています。 ↩