16
23

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

【iOS・GPU】Metalで画像をリサイズする

Last updated at Posted at 2016-12-13

Metalを利用してGPUで高速に画像をリサイズ(拡大・縮小)する方法について書きます。

##要件

  • 既にMTLTextureなので、いったんUIImageCGImageにする、という野暮なことはしたくない
  • CIImageimageByApplyingTransformを利用して簡単にサイズを変換できるし、Metalで処理するようにもできるが、やはり MTLTexture -> CIImage -> (transform) -> CIImage -> MTLTexture というのは遠回りな気がする 1
  • サイズ変更するようなシェーダを自分で書くのはめんどくさい...

MTLTextureを直接拡大・縮小したい 2

##Metal Performance Shadersを利用してMTLTextureをリサイズする

Metal Performance Shaders(以下MPS)フレームワークの MPSImageLanczosScaleというクラスを利用します。

###Lanczosとは

「ランツォシュ」と読みます。

上のツイートに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の項をご参照ください。

  1. もしかしたらいい感じにひとつのパイプラインとして処理されたりして全然遠回りじゃないのかもしれません

  2. 画像ファイルから読み込んだデータやUIImageをMTLTextureオブジェクトに変換する方法はこちらの記事に書いたので今回は省略します。

16
23
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
23

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?