[macOS][Metal]MetalKit入門

<p>Metalについては素人なため入門記事しか書けないので申し訳ない。</p>

<p>まず、サンプルプログラムの名前について説明する。<br />MetalといえばHeavy Metal。ヘビメタのギターといえばIbanezのDestroyer。なのでサンプルプログラムの名前はDestroyer。<br />調べてみたところ、駆逐艦という意味があるそうだ。これには驚いた。</p>

<p>話をサンプルに戻す。まず、MTKViewの派生クラスを用意する。</p>

<pre><code>import Foundation
import MetalKit
 
class DestroyerView: MTKView {
    override func draw(_ dirtyRect: NSRect) {
        super.draw(dirtyRect)
    }
}
</code></pre>

<p>StoryboardでCustom Viewを貼り付け、それのクラスを先ほど作成したクラスに変更する。</p>

<img width="277" alt="Storyboard.png" src="https://qiita-image-store.s3.amazonaws.com/0/12540/6ef0c291-ffb0-0b88-a3cd-99cc04eea092.png">

<p>これで、DestroyerViewで描画されるようになった。次は、Metal関連のコードを記述していく。</p>

<p>頂点と色の情報の構造体を定義する。</p>

<pre><code>struct Vertex {
    var position: vector_float4
    var color: vector_float4
}
</code></pre>

<p>DestroyerViewクラスのメソッドdraw()に描画コードを記述子ていく。</p>

<p>デバイスを作成して設定する。</p>

<pre><code>        self.device = MTLCreateSystemDefaultDevice()
</code></pre>

<p>三角形の頂点と色情報を用意</p>

<pre><code>        /* 三角形の頂点と色情報を用意 */
        let vertexData = [Vertex(position: [-0.8, -0.8, 0.0, 1.0], color: [1.0, 0.0, 0.0, 1.0]),
                          Vertex(position: [ 0.8, -0.8, 0.0, 1.0], color: [0.0, 1.0, 0.0, 1.0]),
                          Vertex(position: [ 0.0,  0.8, 0.0, 1.0], color: [0.0, 0.0, 1.0, 1.0]),]
        let vertexBuffer = device?.makeBuffer(bytes: vertexData,
                                              length: MemoryLayout.size(ofValue: vertexData[0]) * vertexData.count,
                                              options: [])
</code></pre>

<p>シェーダ・ライブラリを取得する。</p>

<pre><code>        guard let library = device?.makeDefaultLibrary() else {
            return
        }
</code></pre>

<p>シェーダーを設定する。</p>

<pre><code>        let vertexFunction = library.makeFunction(name: "vertex_func")
        let fragmentFunction = library.makeFunction(name: "fragment_func")
        let renderPipelineDescriptor = MTLRenderPipelineDescriptor()
        renderPipelineDescriptor.vertexFunction = vertexFunction
        renderPipelineDescriptor.fragmentFunction = fragmentFunction
</code></pre>

<p>RGBA 各8bit形式のピクセルを設定する。</p>

<pre><code>renderPipelineDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm
</code></pre>

<p>パイプラインステートメントを作成する。</p>

<pre><code>            let renderPipelineState = try device?.makeRenderPipelineState(descriptor: renderPipelineDescriptor)
</code></pre>

<p>レンダーパス記述子</p>

<pre><code>            guard let renderPassDescriptor = self.currentRenderPassDescriptor, let drawable = self.currentDrawable else {
                return
            }
</code></pre>

<p>クリアする色を設定</p>

<pre><code>            renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(0.8, 0.7, 0.1, 1.0)
</code></pre>

<p>コマンドキューを生成し、コマンドキューからコマンドバッファを生成する</p>

<pre><code>            let commandBuffer = device?.makeCommandQueue()?.makeCommandBuffer()
</code></pre>

<p>シェーダへデータを送る</p>

<pre><code>            let renderCommandEncoder = commandBuffer?.makeRenderCommandEncoder(descriptor: renderPassDescriptor)
            renderCommandEncoder?.setRenderPipelineState(renderPipelineState!)
            renderCommandEncoder?.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
            renderCommandEncoder?.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 3, instanceCount: 1)
            renderCommandEncoder?.endEncoding()
</code></pre>

<p>コマンドバッファのコミット</p>

<pre><code>            commandBuffer?.present(drawable)
            commandBuffer?.commit()
</code></pre>

<p>シャーダーは、単純に入力値をそのまま返している。</p>

<pre><code>#include &gt;metal_stdlib&lt;
using namespace metal;
 
struct Vertex {
    float4 position [[position]];
    float4 color;
};
 
vertex Vertex vertex_func(constant Vertex *vertices [[buffer(0)]],
                          uint vid [[vertex_id]]) {
    return vertices[vid];
}
 
fragment float4 fragment_func(Vertex vert [[stage_in]]) {
    float3 inColor = float3(vert.color.x, vert.color.y, vert.color.z);
    float4 outColor = float4(inColor.x, inColor.y, inColor.z, 1);
    return outColor;
}
</code></pre>

<p>以下の通り、描画される。</p>

<img width="240" alt="Destroyer.png" src="https://qiita-image-store.s3.amazonaws.com/0/12540/0c0534a8-d5d9-2cda-a1a8-6031ddc0f982.png">

ソースコード
GitHubからどうぞ。<br />
<a href="https://github.com/murakami/workbook/tree/master/mac/Destroyer">https://github.com/murakami/workbook/tree/master/mac/Destroyer</a> - GitHub<br />

関連情報
<a href="https://www.gaprot.jp/pickup/tips/metalkit">MetalKit で GPU を使いこなす</a><br />
<a href="https://qiita.com/advent-calendar/2017/metal">Metal Advent Calendar 2017</a><br />
<a href="https://qiita.com/advent-calendar/2017/cocoa">Cocoa Advent Calendar 2017</a><br />
<a href="https://cocoa-kanto.connpass.com">Cocoa勉強会 BUKURO.swift (connpass)</a><br />
<a href="https://atnd.org/groups/cocoa-study">Cocoa勉強会 BUKURO.swift (ATND)</a><br />
<a href="http://cocoa-study.peatix.com">Cocoa勉強会 BUKURO.swift (Peatix)</a><br />

【Cocoa練習帳】
<a href="http://www.bitz.co.jp/weblog/">http://www.bitz.co.jp/weblog/</a><br />
<a href="http://ameblo.jp/bitz/">http://ameblo.jp/bitz/</a>（ミラー・サイト）<br />
<a href="https://t.co/6P3h2jtbRk>note</a><br />
<a href="https://qiita.com/m_yukio>Qiita</a><br />