SwiftでMetalを使用して三角形の色を変更しよう

Metalを使用して、シェーダーを使って、三角形の色を後から変更する覚書
「SwiftでMetalを使用して三角形を描画しよう」を参照
https://qiita.com/sanoh/items/98df8d580edaa99c32c9

■Step1：シェーダー変更
---
まずはShaderType.h を追加します。
![スクリーンショット 2018-11-26 19.58.30.png](https://qiita-image-store.s3.amazonaws.com/0/122121/3069012f-871b-28ea-4272-f6181f70c1c3.png)

```c:ShaderType.h
#ifndef ShaderType_h
#define ShaderType_h

#ifdef __METAL_VERSION__
#define NS_ENUM(_type, _name) enum _name : _type _name; enum _name : _type
#define NSInteger metal::int32_t
#else
#import <Foundation/Foundation.h>
#endif

#include <simd/simd.h>

typedef NS_ENUM(NSInteger, BufferIndex)
{
    BufferIndexPositions = 0,
    BufferIndexUniforms  = 1
};

typedef struct
{
    float   color[3];
} Uniforms;

#endif /* ShaderType_h */
```
Shader.Metal を編集

```:Shader.metal
#include <metal_stdlib>
#import "ShaderType.h"

using namespace metal;

vertex float4 myVertexShader(const device packed_float3* vertex_array [[ buffer(BufferIndexPositions) ]],
                             unsigned int vid [[ vertex_id ]]) {
    return float4(vertex_array[vid], 1.0);
}

fragment half4 myFragmentShader(constant Uniforms & uniforms [[ buffer(BufferIndexUniforms) ]]) {
    return half4(uniforms.color[0],  uniforms.color[1], uniforms.color[2] , 1.0);
}
```

■Step2:MetalRenderer.swiftを編集します。
---
```swift:MetalRenderer.swift
import Metal
import MetalKit
import simd

let alignedUniformsSize = (MemoryLayout<Uniforms>.size & ~0xFF) + 0x100
let maxBuffersInFlight = 3

class MetalRenderer: NSObject, MTKViewDelegate {
    let _device: MTLDevice
    let _command_queue: MTLCommandQueue
    let _in_flight_semaphore = DispatchSemaphore(value: maxBuffersInFlight)

    let _buffer_position : MTLBuffer!
    let _render_pipeline_state: MTLRenderPipelineState

    let _dynamic_uniform_buffer:MTLBuffer
    var _uniform_buffer_index = 0

    init?(metalKitView: MTKView) {
        let vertices_array:[Float] =
            [
                0.0, 1.0, 0.0,
                -1.0, -1.0, 0.0,
                1.0, -1.0, 0.0
            ]
        _device = metalKitView.device!
        _command_queue = _device.makeCommandQueue()!
        metalKitView.colorPixelFormat = MTLPixelFormat.bgra8Unorm

        let size = vertices_array.count * MemoryLayout<Float>.size
        _buffer_position = _device.makeBuffer(bytes: vertices_array, length: size)
        guard let library = _device.makeDefaultLibrary() else { fatalError() }
        let descriptor = MTLRenderPipelineDescriptor()
        descriptor.vertexFunction=library.makeFunction(name: "myVertexShader")
        descriptor.fragmentFunction=library.makeFunction(name: "myFragmentShader")
        descriptor.colorAttachments[0].pixelFormat = metalKitView.colorPixelFormat
        _render_pipeline_state = try! _device.makeRenderPipelineState(descriptor: descriptor)

        let uniform_buffer_size = alignedUniformsSize * maxBuffersInFlight
        _dynamic_uniform_buffer = _device.makeBuffer(length: uniform_buffer_size, options:[MTLResourceOptions.storageModeShared])!

        super.init()
    }

    func draw(in view: MTKView) {
        _ = _in_flight_semaphore.wait(timeout: DispatchTime.distantFuture)

        if let command_buffer = _command_queue.makeCommandBuffer() {
            let semaphore = _in_flight_semaphore
            command_buffer.addCompletedHandler { (_ command_buffer)-> Swift.Void in
                semaphore.signal()
            }
            let render_pass_descriptor = view.currentRenderPassDescriptor
            render_pass_descriptor?.colorAttachments[0].loadAction=MTLLoadAction.clear
            render_pass_descriptor?.colorAttachments[0].storeAction=MTLStoreAction.store
            render_pass_descriptor?.colorAttachments[0].clearColor=MTLClearColorMake(1.0, 0.0, 0.0, 1.0)
            if let render_pass_descriptor = render_pass_descriptor {
                _uniform_buffer_index = (_uniform_buffer_index + 1) % maxBuffersInFlight
                let uniform_buffer_offset = alignedUniformsSize * _uniform_buffer_index
                let uniforms = UnsafeMutableRawPointer(_dynamic_uniform_buffer.contents() + uniform_buffer_offset).bindMemory(to:Uniforms.self, capacity:1)
                uniforms[0].color.0 = 0.0   //  R   色指定
                uniforms[0].color.1 = 0.0   //  G
                uniforms[0].color.2 = 1.0   //  B

                if let render_encoder = command_buffer.makeRenderCommandEncoder(descriptor: render_pass_descriptor) {

                    render_encoder.setFragmentBuffer(_dynamic_uniform_buffer, offset:uniform_buffer_offset, index: BufferIndex.uniforms.rawValue)

                    render_encoder.setRenderPipelineState(_render_pipeline_state)
                    render_encoder.setVertexBuffer(_buffer_position, offset: 0, index: BufferIndex.positions.rawValue)
                    render_encoder.drawPrimitives(type: MTLPrimitiveType.triangle, vertexStart: 0, vertexCount: 3)

                    render_encoder.endEncoding()
                    if let drawable = view.currentDrawable {
                        command_buffer.present(drawable)
                    }
                }
            }
            command_buffer.commit()
        }
    }

    func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
    }
}
```

■Setp3：ヘッダーファイルをSwiftから参照できるようにする。
---
Swiftからは直接ヘッダーファイルを参照できないので、設定します。
「Build Setting」の「Objective-C Bridging Header」に「ShaderType.h」を設定します。
![スクリーンショット 2018-11-26 19.59.22.png](https://qiita-image-store.s3.amazonaws.com/0/122121/ec83e96d-502a-35fd-030f-cf55534eacdc.png)

■Setp4：実行
---
iOsは実機で実行してくださいMacの場合は以下のようになります。
![スクリーンショット 2018-11-26 19.56.26.png](https://qiita-image-store.s3.amazonaws.com/0/122121/1d22aa4b-7a99-06a2-0102-cb57c20d39e4.png)