Metalで遊ぶ「Rendererを作る」

この記事は[NCC Advent Calendar 2018](https://qiita.com/advent-calendar/2018/ncc)の1日目の記事です.

# NCCとは
1日目ですし，NCCの説明から．
NCCは明治大学の公認サークルです．プログラミングを中心とした技術系の情報・知見共有を目的としたサークルで，弊学総合数理学部のメンバーが中心となっています．

# 勝手にやってるMetal連載

最近Metalを初めて楽しくなっちゃったのでたくさんoutputしてみる会です．

1. [Rendererを作る] ←　イマココ
2. [プロシージャルモデリング的な何か](https://qiita.com/micky_dtl/items/711f3f6c86abfe0bea89)
3. [フラグメントシェーダで遊ぶ](https://qiita.com/micky_dtl/items/8926294af4ed81f69f76)
4. [テクスチャで遊ぶ](https://qiita.com/micky_dtl/items/f4f8b09e7478187c9ea5)

# Metalをやりはじめた
[去年のアドベントカレンダー](https://qiita.com/micky_dtl/items/b426a26a9e94a8e5fb4c)でも，CreativeCodingな記事を書きましたが，今年はshaderです．
Processingで書くのも楽しいですが，GLSLにも手を出そうと少し前にShaderを始めました.もともとiOSエンジニアで，Swift1.0の頃から戯れていた身からするとMetalに手を出すのは必至だったわけです．

ということで，今年の弊サークルのアドベントカレンダーで連投しようかなというわけです．

でも，GPUプログラミング，Rendererを作るだけでなかなかしんどい．(WebGLわからん)
なので，連投だし，今日はRendererでいいかなあって（）
ほぼ同じコードをgithubにあげておくので連投と共にcommitもします！

# Metalを一緒にやろう！

## GPUを使ったレンダリングの流れ

* 頂点を用意
    * **ローカル座標**と呼ぶ
* VertexShader
    * ローカル座標を変換する
* FragmentShader
    * 色を付ける

流れはこれだけです．
このVertexShaderとFragmentShaderを用意して描画するところで，MetalのAPIを叩くわけです．
この辺をまとめて`Renderer`として抜き取っちゃいます．


## Rendererで使うものたち
よく使うオブジェクトについて説明していきます．
コードを書くときに，ただの社協になったりするとつまらないので頑張って書きます笑.
基本的に[MetalのDeveloper Documentation](https://developer.apple.com/documentation/metal)から引用しています．

一度iPadを使ってこういうものを書いてみたかったので，ついでにあげてみます...笑

<img width="1024" alt="IMG_DFF0D03B990D-1.jpeg" src="https://qiita-image-store.s3.amazonaws.com/0/76493/28a65d17-bffe-17a9-8ebd-96c76456142a.jpeg">

これらを実現するMetalAPIたちです．

### MTKView

> An object that implements the MTKViewDelegate protocol can be set as a MTKView object’s delegate. A delegate allows your Metal application to provide a drawing method to a MTKView object and respond to rendering events without subclassing the MTKView class.

MetalKitに実装されているViewです．
MTKViewDelegateに遵守したオブジェクトを利用して，MTKViewに描画することができます．

作成するRendererもこのDelegateに遵守させます．

### MTLDevice
> The **MTLDevice protocol defines the interface to a GPU**. You can query a GPU device for the unique capabilities it offers your Metal app, and use the GPU device to issue all of your Metal commands.

MTLDeviceを通して，GPUにアクセスすることができます．(引用の太字部分)
正確には，GPUにアクセスするためのパイプラインを構築するために利用します．

MTLDeviceは，MTKViewを表示するViewControllerの`viewDidLoad`内で生成してRendererをinitする際に渡します．


### MTLBuffer
>The MTLBuffer protocol defines the interface for objects that represent an allocation of unformatted, device-accessible memory that can contain any type of data.

`allocation of unformatted`とは結構迷ったのですが，おそらく分割されていない割り当てられたデータです．そんでもって，Swiftで表現できる型ならば，構造体でも`MTLBuffer`にすることができます．
MTLBufferはMTLDeviceの`makeBuffer(略)`というメソッドを用いて作成します．この関数の引数は色々あります

**勝手な解釈**
`分割されていない`というのは，GPU側でデータを受け取る際の解釈の仕方でそのデータの型が決まります．
例えば，n個の`float4`の配列をMTLBufferにしても，分割されていないので外から見れば `float `が$4n$個並んでいるだけです．
「`float3`で受け取る！」と決めれば，先頭の方から3個ずつデータを受け取れる気がします．

MTLBufferに対して，MTLTextureが`An allocation of formatted image data`と説明されているからです．

### MTLLibrary
> A MTLLibrary object contains Metal shading language source code that is compiled during the app build process or at runtime from a text string.

基本的に，コンパイルされたMetal shading language(MSL)のコードにアクセスするために使います．
こちらもMTLDeviceから`makeDefaultLibrary()`というメソッドを用いて作成します．

### MTLFunction
この関数が，GPUの各スレッドで実行されるやつです．
MSLでサポートされている関数,すなわちMTLFunctionは以下の3種類です

* Vertex
* Fragment
* Kernel

VertexFunctionとFragmentFunctionは後述のMTLRenderPipelineStateと結びついています.描画するためのshader関数です．
今回は詳しく扱いませんが，KernelFunctionはMTLComputePipelineStateと結びついています.GPGPU，ComputeShaderと言われる類のshader関数です．

どれもMTLLibraryから`makeFunction(name: String)`メソッドを用いて関数を呼び出し，対応するPipelineと結びつけます．

* MTLRenderPipelineDescriptor

* MTLRenderPassDescriptor

### MTLRenderPipelineState

> The MTLRenderPipelineState protocol defines the interface for a lightweight object used to encode the state for a configured graphics rendering pipeline.

とか

> The MTLRenderPipelineDescriptor sets up a prevalidated state.



MTLRenderPipelineDescriptorをもとに作成されます．


### MTLCommandQueue

> command queue is an expensive object to create, so you want to create only one and reuse it.

コマンドキューを生成するのは大変だから使いまわせと

### MTLCommandBuffer

> command buffer objects is cheap to create.

コマンドバッファーを作るのは簡単だから，毎フレーム作っていいよと

### MTLRenderCommandEncoder

GPUにデータを渡す(バインドする)役割を担ったり
最後の最後の`drawPrimitives`もここですね

## Rendererを作る

``` Renderer.swift

import Foundation
import Metal
import MetalKit


class Renderer:NSObject {
    // local datas
    private var vertices: [Vertex]!
    private var vertexBuffer: MTLBuffer!
    private var indices: [UInt16]!
    private var indexBuffer: MTLBuffer!
    // for MetalAPI
    private var mtlDevice: MTLDevice!
    private var mtkview: MTKView!
    
    private var commandQueue: MTLCommandQueue!
    private var pipelineState: MTLRenderPipelineState!
    
    init(metalKitView mtkView: MTKView) {
        super.init()
        self.mtkview = mtkView
        
        guard let device = mtkView.device else {
            print("mtkview doesn't have mtlDevice")
            return
        }
        
        self.mtlDevice = device
        
        commandQueue = device.makeCommandQueue()
        vertices = [Vertex]()
        indices = [UInt16]()
    }
    
    private func buildPipeline() {
        let library = mtlDevice.makeDefaultLibrary()
        let vertexFunction = library?.makeFunction(name: "vertexDay1")
        let fragmentFunction = library?.makeFunction(name: "fragmentDay1")
        
        let pipelineDescriptor = MTLRenderPipelineDescriptor()
        pipelineDescriptor.vertexFunction = vertexFunction
        pipelineDescriptor.fragmentFunction = fragmentFunction
        pipelineDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm
        
        do {
            try pipelineState = mtlDevice.makeRenderPipelineState(descriptor: pipelineDescriptor)
        } catch let error {
            print("Failed to create pipeline state, error: ", error)
        }
    }
    
    private func buildBuffer() {
        vertexBuffer = mtlDevice.makeBuffer(bytes: vertices, length: vertices.count*MemoryLayout<Vertex>.stride, options: [])
        indexBuffer = mtlDevice.makeBuffer(bytes: indices, length: indices.count*MemoryLayout<UInt16>.stride, options: [])
    }
    
}

extension Renderer: MTKViewDelegate{
    
    public func draw(in view: MTKView) {
        guard let drawable = view.currentDrawable,
            let renderPassDescriptor = view.currentRenderPassDescriptor else {
                print("cannot get drawable or renderPassDescriptor")
                return
        }
        let commandBuffer = commandQueue.makeCommandBuffer()
        let commandEncoder = commandBuffer?.makeRenderCommandEncoder(descriptor: renderPassDescriptor)
        commandEncoder?.setRenderPipelineState(pipelineState)
        commandEncoder?.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
        commandEncoder?.drawIndexedPrimitives(type: .triangle, indexCount: indices.count, indexType: .uint16, indexBuffer: indexBuffer, indexBufferOffset: 0)
        commandEncoder?.endEncoding()
        commandBuffer?.present(drawable)
        commandBuffer?.commit()
        
    }
    
    public func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
        
    }
    
}

// ViewController(Rendererを宣言する場所から呼び出すメソッドたち)
extension Renderer {
    public func setVertices(_ vertices: [float3]) {
        self.vertices += vertices.map({ (pos) -> Vertex in
            return Vertex(position: pos)
        })
    }
    public func setIndices(_ indices: [Int]) {
        self.indices += indices.map({ (n) -> UInt16 in
            return UInt16(n)
        })
    }

    public func applyTouch(touch: float2) {
        uniforms.touch = touch
    }
    
    public func start () {
        buildBuffer()
        buildPipeline()
        mtkview.delegate = self
    }
}

struct Vertex {
    var position: float3
}

```

## よくあるUniform変数を作る

* time
* mouse
* アスペクト比
    * スクリーン場の座標が，長方形ですがx,y共に $-1 \le x, y \le 1$ となっているので，縦に歪んでしまいます
    * スクリーン座標系に戻してあげましょう
    * [第三回](https://qiita.com/micky_dtl/items/8926294af4ed81f69f76)で図とともに比較的詳しく説明しています．

これを構造体で定義しておいて，それをそのまま渡しましょう．
受け取り先のshaderコードにも同じ構造体を定義します．

``` Uniforms.swift

struct Uniforms {
    var time: Float
    var aspectRatio: Float
    var touch: float2
}

```

``` Renderer.swift
    
    private var preferredFramesTime: Float! // 毎フレームにかかる秒数

    init(metalKitView mtkView: MTKView) {
        super.init()
        // ~~ 略 ~~
        uniforms = Uniforms(times: Float(0.0), aspectRatio: Float(0.0), touch: float2())
        uniforms.aspectRatio = Float(mtkView.frame.size.width / mtkView.frame.size.height)
        uniforms.time = 0.0
        preferredFramesTime = 1.0 / Float(mtkView.preferredFramesPerSecond)
    }
    
    public func draw(in view: MTKView) {
        uniforms.time += preferredFramesTime
        // ~~ 略 ~~
        commandEncoder?.setVertexBytes(&uniforms, length: MemoryLayout<Uniforms>.stride, index: 1)
    }
```

``` ViewController.swift
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        if(touches.first == nil) { return }
        let loc = touches.first!.location(in: view)
        let resolution = view.frame.size
        renderer.applyTouch(touch: float2(Float((loc.x/resolution.width)*2.0-1.0),-Float((loc.y/resolution.height)*2.0-1.0)))
    }

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        // 同じ
    }
```

shaderのコードはこんな感じです

``` shaders.metal
#include <metal_stdlib>
using namespace metal;

struct VertexIn {
    float3 pos;
};

struct Uniforms {
    float time;
    float aspectRatio;
    float2 touch;
};
struct VertexOut {
    float4 pos [[ position ]];
    float3 color;
    float time;
};

vertex VertexOut vertexDay1(constant VertexIn *vertexIn [[buffer(0)]],
                            constant Uniforms &uniforms [[buffer(1)]],
                            uint vid [[ vertex_id ]]) {
    float3 pos = vertexIn[vid].pos;
    VertexOut out;
    
    out.pos = float4(pos,1.0);
    out.color = float3((pos.xy+float2(1.0))/2.0, 0.7);
    out.time = uniforms.time;
    
    return out;
}

fragment half4 fragmentDay1(VertexOut vertexIn [[stage_in]]) {
    float4 p = vertexIn.pos;
    float3 c = vertexIn.color;
    float t = vertexIn.time;
    return half4(c.x, c.y, (1.0+cos(t))/2.0,1.0);
}

```
次の間を行き来するようなグラデーションのアニメーションになります！

<img width="600" alt="Gradation with Metal" src="https://gyazo.com/9dc979af2365686ec2f2a5ef69d3b374.raw">

## Swift書く人がいない

弊サークルにiOSアプリ作ってる人がいません．誰かやろう

## コード

今日のコードをは[このリポジトリ](https://github.com/math-miki/MetalAdventCalendar)のDay1に入っています．