Metalを利用してGPUで高速に画像をリサイズ(拡大・縮小)する方法について書きます。
##要件
- 既に
MTLTexture
なので、いったんUIImage
やCGImage
にする、という野暮なことはしたくない -
CIImage
もimageByApplyingTransform
を利用して簡単にサイズを変換できるし、Metalで処理するようにもできるが、やはり MTLTexture -> CIImage -> (transform) -> CIImage -> MTLTexture というのは遠回りな気がする 1
- サイズ変更するようなシェーダを自分で書くのはめんどくさい...
→ MTLTexture
を直接拡大・縮小したい 2
##Metal Performance Shadersを利用してMTLTextureをリサイズする
Metal Performance Shaders(以下MPS)フレームワークの MPSImageLanczosScale
というクラスを利用します。
###Lanczosとは
「ランツォシュ」と読みます。
「ランツォシュ」って読むのか。 / Lanczos関数による画像の拡大縮小 https://t.co/uOcwM4DctI
— Shuichi Tsutsumi (@shu223) 2016年8月28日
上のツイートにURLを貼っている記事によると、Lanczosはこのアルゴリズムを考えた人の名前で、高品質な画像を得られる拡大縮小アルゴリズム(関数?)とのことです。
###MPSImageLanczosScale
を用いたリサイズ実装方法
こちらの回答を参考にしました。
let filter = MPSImageLanczosScale(device: device)
var transform = MPSScaleTransform(scaleX: scaleX, scaleY: scaleY, translateX: translateX, translateY: translateY)
let commandBuffer = commandQueue.makeCommandBuffer()
withUnsafePointer(to: &transform) { (transformPtr: UnsafePointer<MPSScaleTransform>) -> () in
filter.scaleTransform = transformPtr
filter.encode(commandBuffer: commandBuffer, sourceTexture: sourceTexture, destinationTexture: destTexture)
}
commandBuffer.commit()
commandBuffer.waitUntilCompleted()
要は、
-
MPSImageLanczosScale
を初期化して、 - その
scaleTransform
プロパティに、変換行列MPSScaleTransform
のポインタを渡して、 -
encodeWithCommandBuffer:sourceTexture:destinationTexture
メソッドを呼んで、 - コマンドバッファをコマンドキューにプッシュしているだけ
です。
withUnsafePointer
のあたりでパッと見ちょっと難しそうな気がしますが、filter.scaleTransform = &transform
ってやりたいけどSwiftがそうさせてくれないのでこういうことになってるだけで、やりたいこととしてはすごくシンプルです。
encode〜
のメソッドは、前回記事でコマンドエンコーダを生成してシェーダで処理する入・出力テクスチャをセットしてendEncodingする一連の流れに相当するのかなと思います。
let encoder = commandBuffer.makeComputeCommandEncoder()
encoder.setComputePipelineState(pipeline)
encoder.setTexture(texture, at: 0)
encoder.setTexture(drawable.texture, at: 1)
encoder.dispatchThreadgroups(threadgroupsPerGrid, threadsPerThreadgroup: threadsPerThreadgroup)
encoder.endEncoding()
こうやってみると、MPSフレームワークがシェーダまわりの処理を簡易化してくれるものであることが実感できますね。もちろん、"Performance"とフレームワーク名についているぐらいなので、中身は中の人がカリカリに最適化したものと期待できます。
##MPS利用のデメリット?
使いやすくて速いなんてMPSが最高っぽいですが、MPSはMetalとはまた別のフレームワークでして、MetalをサポートしていてもMPSをサポートしていないデバイスというのもあります。(例: 5s)
詳しくは下記記事のMetal Feature Setsの項をご参照ください。