Metal逆引きレシピ

随時追加していきます。

## `MTLBuffer`の内容を更新する

`makeBuffer`でバッファを新規作成するのではなく、既にあるバッファの中身を変更したい場合、`UnsafeMutableRawPointer`の`copyMemory`メソッドを使用する

```swift
buffer.contents().copyMemory(from: &value, byteCount: MemoryLayout<Float>.stride)
```

## `MTLTexture`をコピーする

たとえば`MTKView`で画像ファイルから読み込んできたテクスチャ（`MTLTexture`）を描画したいときに、

```swift
currentDrawable.texture = aTexture
```

みたいなことは**できない**。

[この記事](http://qiita.com/shu223/items/3301a1e64757c0bd73ef)でやったみたいに、**パススルーシェーダを書いて書き込む方法**もあるが、もっと手軽な方法として、 `MTLBlitCommandEncoder`を使う方法がある。

```swift
let commandBuffer = commandQueue.makeCommandBuffer()

let blitEncoder = commandBuffer.makeBlitCommandEncoder()
blitEncoder.copy(from: fromTexture,
                 sourceSlice: 0,
                 sourceLevel: 0,
                 sourceOrigin: MTLOrigin(x: 0, y: 0, z: 0),
                 sourceSize: MTLSizeMake(fromTexture.width, fromTexture.height, fromTexture.depth),
                 to: drawable.texture,
                 destinationSlice: 0,
                 destinationLevel: 0,
                 destinationOrigin: MTLOrigin(x: 0, y: 0, z: 0))
blitEncoder.endEncoding()

commandBuffer.present(drawable)
commandBuffer.commit()
commandBuffer.waitUntilCompleted()
```

- 参考： [Display Images with MTKView](http://metalcvexamples.com/metal-cv-basics/2015/12/13/display-images-with-mtkview.html)


## MTLTextureをリサイズする

Metal Performance Shadersフレームワークの`MPSImageLanczosScale`というクラスを利用する。

下記記事を参照：
- [Metalで画像をリサイズする - Qiita](http://qiita.com/shu223/items/19ac718dcd561a891689)


## `MTLTexture`を新規生成する

画像ファイルから`MTKTextureLoader`で読み込んでくるのではなく、新規でテクスチャを生成するには、`MTLTextureDescriptor`を使用する。

```swift
let textureDescriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: pixelFormat, width: width, height: height, mipmapped: true)
let texture = device.makeTexture(descriptor: textureDescriptor)
```

## `UIImage`を`MTLTexture`に変換する

`image` という`UIImage`オブジェクトがあるとして、`MTKTextureLoader`を用いて次のように処理する。

```swift
guard let cgImage = image?.cgImage else {return}    
try? texture = textureLoader.newTexture(with: cgImage, options: do 
```

## アセットから`MTLTexture`を生成する

アセットから読み込んですぐ描画したいのであれば、いったんUIImageオブジェクトをつくるのはCPUのムダ。`MTKTextureLoader` には下記のようにasset nameを直接渡してテクスチャを生成するメソッドも用意されている。

```swift
func newTexture(withName name: String, scaleFactor: CGFloat, bundle: Bundle?, options: [String : NSObject]? = nil) throws -> MTLTexture
```


## iOSデバイスがMetal Performance Shadersをサポートしているかを確認する

[この記事](http://qiita.com/shu223/items/8027201e50c68288f0be)の『Metal Feature Sets』の項に書いたとおり、当該iOSデバイスが、**MetalをサポートしていてもMPSはサポートしていない**こともありえます。（例：iPhone 5s）

以下のコードでチェックできます。

```swift
guard MPSSupportsMTLDevice(device) else {
    print("Metal Performance Shaders not Supported on current Device")
    return
}
```

## `MTLTexture`にブラーをかける

Metal Performance Shadersフレームワークの`MPSImageGaussianBlur`というクラスを利用する。

**入力テクスチャ／出力テクスチャをそれぞれ指定**する方法と、**入力テクスチャそのものに出力（in place）**する方法とがある。

たとえば`MTKView`の`currentDrawable.texture`に既に書き込まれているテクスチャにブラーを書けたい場合、in placeで以下のように処理できる。

```swift
guard let drawable = currentDrawable else {
    return
}
let blur = MPSImageGaussianBlur(device: device, sigma: blurRadius)
let commandBuffer = commandQueue.makeCommandBuffer()
var drawableTexture = drawable.texture
_ = withUnsafeMutablePointer(to: &drawableTexture) { (texturePtr: UnsafeMutablePointer<MTLTexture>) in
    blur.encode(commandBuffer: commandBuffer, inPlaceTexture: texturePtr, fallbackCopyAllocator: nil)
}
commandBuffer.commit()
commandBuffer.waitUntilCompleted()
```

入力／出力をそれぞれ指定する場合は以下のようにする。

```swift
let blur = MPSImageGaussianBlur(device: device, sigma: blurSigma)
let commandBuffer = commandQueue.makeCommandBuffer()
blur.encode(commandBuffer: commandBuffer, sourceTexture: sourceTexture, destinationTexture: destinationTexture)
commandBuffer.commit()
commandBuffer.waitUntilCompleted()
```

![blur.gif](https://qiita-image-store.s3.amazonaws.com/0/3180/b8e703bd-fa27-9e65-d9e1-6e62b016b79c.gif)

（シグマ0〜100〜0を**60 fps**で変化させてみた様子。AnimatedGIFというフォーマットの都合上、グラデーションが汚いですが、実際はすごく綺麗です。）


## `MTLTexture`の転置（行と列の入れ替え）

Landscapeで入ってきたカメラ入力をPortraitにしたい場合とかに。`MPSImageTranspose`を使う。引数等は一切なし。

```objc:objc
MPSImageTranspose *transpose =[[MPSImageTranspose alloc] initWithDevice:commandQueue.device];
[transpose encodeToCommandBuffer:commandBuffer
                   sourceTexture:scrTexture
              destinationTexture:dstTexture];
```


## `MTLTexture` -> `CIImage`

CIImageにはMTLTextureを引数に渡せるイニシャライザが用意されているが、

```swift
let inputImage = CIImage(mtlTexture: texture, options: nil)
```

これだけだと**上下反転**した画像ができてしまう。

というわけで以下のようにy方向にフリップさせる。

```swift
let inputImage = CIImage(mtlTexture: texture, options: nil)?.applying(CGAffineTransform(scaleX: 1, y: -1).translatedBy(x: 0, y: CGFloat(texture.height)))
```

## `CIImage` -> `MTLTexture`

### Metalを利用

Metalを利用してレンダリングするためのCIContextを用意しておく。

```swift
context = CIContext(mtlDevice: device)
```

`CIImage`の内容を`MTLTexture`に描画する。

```swift
let colorSpace = CGColorSpaceCreateDeviceRGB()
context.render(inputImage, to: toTexture, commandBuffer: commandBuffer, bounds: inputImage.extent, colorSpace: colorSpace)
```

コマンドバッファの指定はオプショナル。コマンドバッファを渡す場合はcommitもやる。



### Core Graphicsを利用

`CGImage`に変換して`MTKTextureLoader`を用いて`MTLTexture`をロードする

```swift
let context = CIContext(options:nil)
guard let cgImage = context.createCGImage(image, from: image.extent) else {return}
let texture = try? textureLoader.newTexture(cgImage: cgImage)
```

## `CIImage`を`MTKView`に描画する

上記の応用例。`MTKView`の`currentDrawable`の`texture`に`CIImage`を書き込み、`present`する。

```swift
let commandBuffer = commandQueue.makeCommandBuffer()

let colorSpace = CGColorSpaceCreateDeviceRGB()
context.render(outputImage, to: drawable.texture, commandBuffer: commandBuffer, bounds: outputImage.extent, colorSpace: colorSpace)

commandBuffer.present(drawable)
commandBuffer.commit()
commandBuffer.waitUntilCompleted()
```



## `CMSampleBuffer` -> `CVImageBuffer` (`CVPixelBuffer`) -> `MTLTexture`

AVFoudationを用いて得られるカメラ入力をMetalでリアルタイム処理する際にこの変換が必要になる。詳しくは以下の記事を参照：

- [Metalでカメラからの動画入力をリアルタイム処理する - Qiita](http://qiita.com/shu223/items/2e493645e2d9a1e35a0d)

ここにはコードだけ貼っておく（リンク先にはObjective-C版のコードもあり）

- `CVImageBuffer` (`CVPixelBuffer`)を取り出す

```swift
let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)
```

- `CVMetalTextureCache`を用意

```swift
var textureCache : CVMetalTextureCache?
```

```swift
CVMetalTextureCacheCreate(kCFAllocatorDefault, nil, device, nil, &textureCache)
```

- `CVMetalTextureCache`を用いて、 `CVImageBuffer` から、 `CVMetalTexture` を生成

```swift:swift
let width = CVPixelBufferGetWidth(imageBuffer)
let height = CVPixelBufferGetHeight(imageBuffer)
    
var imageTexture: CVMetalTexture?
    
let result = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, textureCache, imageBuffer, nil, pixelFormat, width, height, 0, &imageTexture)
```

- `CVMetalTexture`から、`MTLTexture` を取り出す

```swift:swift
let texture = CVMetalTextureGetTexture(imageTexture)
```


### `CVMetalTextureCache`を利用しない方法

iOS 11だと`MTLTexture`の`usage`を指定しないと実行時エラーになるようになって、`CVMetalTextureCache`を使う場合にそれを指定する方法がわからなかった。`MTLTextureDescriptor`から生成する場合はその`usage`に指定するだけ。

というわけで`CVMetalTextureCache`を使いたくない場合、

```
let image = CIImage(cvPixelBuffer: buffer)
```

あとは「CIImage -> MTLTexture」 の方法で変換できる（この方法がさらに２種類ある。別項に記載済み）


## Core ImageとMetalを併用する

MTLTexture -> CIImage -> (CIFilterで画像処理) -> MTLTexture -> MTKViewに描画

という流れ。Core ImageはMetalとシームレスに統合できるよう実装されているので、CPUとGPUを行ったり来たりするようなムダはない。詳しくはこちらの記事を参照： [Metalの恩恵は受けつつCore Imageで「手軽に」画像処理 - Qiita](http://qiita.com/shu223/items/4c0f8073477f787e1627)

![coreimage.gif](https://qiita-image-store.s3.amazonaws.com/0/3180/f5da7f90-a2ff-5bd6-a784-9b8004bfee9f.gif)

（Core Imageの`CIPixellate`フィルタをパラメータを変えつつ**60fps**でMTKViewに描画）


## MetalシェーダでSceneKitのマテリアルを描画する

SCNProgramというクラスを使う。

```swift
let program = SCNProgram()
program.fragmentFunctionName = "myVertex"
program.vertexFunctionName = "myFragment"
material.program = program
```


詳細は下記記事を参照：

- [MetalシェーダでSceneKitのマテリアルを描画する - Qiita](http://qiita.com/shu223/items/b5729fdf1d95721d07b7)

![color.gif](https://qiita-image-store.s3.amazonaws.com/0/3180/4aead202-00e0-20e7-ffad-3208c31ab041.gif)

（http://glslsandbox.com/e#36858.0 のGLSLをMSLに移行しつつSceneKitに描画）

![voronoi.gif](https://qiita-image-store.s3.amazonaws.com/0/3180/d16268ae-628e-9e32-7183-39e75912d68f.gif)

（http://glslsandbox.com/e#37017.0 のGLSLをMSLに移行しつつSceneKitに描画）