Metalで画像の描画

画像処理でピクセル単位でいじっていると処理が重いため、Metalを学んでみることにしました。

基本的な流れ

Metal処理の大まかな流れとして以下のような感じで流れていきます。

  1. GPUのアクセスを行うためのMTLCreateSystemDefaultDeviceのインスタンスを作成
  2. データを送るための指定方法commandQueueをデバイスを使って作成
  3. GPUへ送るCommandBufferを作成
  4. Encoderで送る内容の詳細を定義

1.GPUのアクセスを行うためのMTLCreateSystemDefaultDeviceのインスタンスを作成

let device = MTLCreateSystemDefaultDevice()!

Metalから実機のGPUへアクセスするためのデバイスのインスタンスを作成します。

2.データを送るための指定方法commandQueueをデバイスを使って作成

commandQuereという言葉自体がよくわからなかったので調べてみると以下のような意味のようです。

ファイル転送、コマンドライン処理、またはセッション終了コマンドなど、複数のコマンドの順序を指定する機能。

http://jp.norton.com/security_response/glossary/define.jsp?letter=c&word=command-queue

MetalはCPUからCommand BufferというものをGPUに送って処理を行うものです。commandQuereはどのようにどんなコマンドで送るかを指定しているのだと思います。

var commandQueue:MTLCommandQueue! = device.makeCommandQueue()

3.GPUへ送るCommandBufferを作成

GPUにデータを送るためのファイル(command buffer)の生成です。

 let commandBuffer = commandQueue.makeCommandBuffer()

4.Encoderで送る内容の詳細を定義

データを送るファイル(command buffer)の中身を定義するためにEncoderを作成します。

今回は元の画像のデータを何も変更せずGPUに送っています。

let blitEncoder = commandBuffer?.makeBlitCommandEncoder()
        
blitEncoder?.copy(
    //コピーするtexture
    from: texture,sourceSlice: 0,sourceLevel: 0,
    sourceOrigin: MTLOrigin(x: 0, y: 0, z: 0),
    sourceSize: MTLSizeMake(w, h, texture.depth),
    //コピー保存先
    to: drawable.texture,destinationSlice: 0,destinationLevel: 0,
    destinationOrigin: MTLOrigin(x: 0, y: 0, z: 0))

コード一覧

import UIKit
import MetalKit

class ViewController: UIViewController {
   
   @IBOutlet weak var mtlView: MTKView! {
        didSet {
            mtlView.device = device
            mtlView.delegate = self
        }
    }
    
    private let device = MTLCreateSystemDefaultDevice()!
    private var commandQueue: MTLCommandQueue!
    private var texture: MTLTexture!

    override func viewDidLoad() {
        super.viewDidLoad()
       
        commandQueue = device.makeCommandQueue()
        
        //画像の読み込みとフォーマット合わせ
        let textureLoader = MTKTextureLoader(device: device)
        texture = try! textureLoader.newTexture(name: "img",scaleFactor: view.contentScaleFactor, bundle: nil )
        mtlView.colorPixelFormat = texture.pixelFormat
        
    }
}
extension ViewController: MTKViewDelegate {
    func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
    }
    
    //Metalの描画処理を行うメソッド
    func draw(in view: MTKView) {
        let drawable = view.currentDrawable!
        let commandBuffer = commandQueue.makeCommandBuffer()
        
        let w = min(texture.width, drawable.texture.width)
        let h = min(texture.height, drawable.texture.height)
        
        let blitEncoder = commandBuffer?.makeBlitCommandEncoder()
        
        blitEncoder?.copy(
            //コピーするtexture
            from: texture,sourceSlice: 0,sourceLevel: 0,
            sourceOrigin: MTLOrigin(x: 0, y: 0, z: 0),
            sourceSize: MTLSizeMake(w, h, texture.depth),
            //コピー保存先
            to: drawable.texture,destinationSlice: 0,destinationLevel: 0,
            destinationOrigin: MTLOrigin(x: 0, y: 0, z: 0))
        
        blitEncoder?.endEncoding()
        
        commandBuffer?.present(drawable)
        commandBuffer?.commit()
    }
}

参考文献

MetalBook

command queue (コマンドキュー)

エンコードとは?動画を扱うなら知っておきたいエンコードの基本について。