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

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

■Step1：シェーダー変更
---
まずはShaderType.h を追加します。
![スクリーンショット 2018-11-26 19.56.03.png](https://qiita-image-store.s3.amazonaws.com/0/122121/e49e7998-7e26-4abc-806e-5fdb7e968943.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);
}
```
■Steop2:MetalRenderer.mを編集します。
---
```objective_c:MetalRenderer.m
#import "MetalRenderer.h"
#import "ShaderType.h"

static const NSUInteger MaxBuffersInFlight = 3;
static const size_t kAlignedUniformsSize = (sizeof(Uniforms) & ~0xFF) + 0x100;

@implementation MetalRenderer
{
    dispatch_semaphore_t _in_flight_semaphore;
    id <MTLDevice> _device;
    id <MTLCommandQueue> _command_queue;

    id <MTLRenderPipelineState> _pipeline_state;
    id <MTLBuffer>   _buffer_position;

    id <MTLBuffer> _dynamic_uniform_buffer; //＜新規＞
    uint8_t _uniform_buffer_index;          //＜新規＞
}

-(nonnull instancetype)initWithMetalKitView:(nonnull MTKView *)view;
{
    //頂点データ
    float vertices_array[]={
        0.0, 1.0, 0.0,
        -1.0, -1.0, 0.0,
        1.0, -1.0, 0.0,
    };
    if(self= [super init])
    {
        _device = view.device;
        _in_flight_semaphore = dispatch_semaphore_create(MaxBuffersInFlight);
        view.colorPixelFormat = MTLPixelFormatBGRA8Unorm;
        _command_queue = [_device newCommandQueue];
        //頂点情報の登録
        NSInteger size = sizeof(vertices_array);
        _buffer_position=[_device newBufferWithBytes:vertices_array
                                              length:size
                                             options:MTLResourceOptionCPUCacheModeDefault];
        //シェーダーの登録
        id <MTLLibrary> library = [_device newDefaultLibrary];
        MTLRenderPipelineDescriptor *descriptor = [[MTLRenderPipelineDescriptor alloc] init];
        descriptor.vertexFunction = [library newFunctionWithName:@"myVertexShader"];
        descriptor.fragmentFunction = [library newFunctionWithName:@"myFragmentShader"];
        descriptor.colorAttachments[0].pixelFormat=MTLPixelFormatBGRA8Unorm;
        NSError *error = NULL;
        _pipeline_state = [_device newRenderPipelineStateWithDescriptor:descriptor 
                                                                  error:&error];
        if (!_pipeline_state)
        {
            NSLog(@"Failed to created pipeline state, error %@", error);
        }
        //パラメータバッファの登録＜新規＞
        NSUInteger uniform_buffer_size = kAlignedUniformsSize * MaxBuffersInFlight;
        _dynamic_uniform_buffer = [_device newBufferWithLength:uniform_buffer_size
                                                       options:MTLResourceStorageModeShared];
    }
    return self;
}

- (void)drawInMTKView:(nonnull MTKView *)view
{
    dispatch_semaphore_wait(_in_flight_semaphore, DISPATCH_TIME_FOREVER);

    id <MTLCommandBuffer> command_buffer = [_command_queue commandBuffer];
    __block dispatch_semaphore_t block_sema = _in_flight_semaphore;
    [command_buffer addCompletedHandler:^(id<MTLCommandBuffer> buffer)
     {
         dispatch_semaphore_signal(block_sema);
     }];
    MTLRenderPassDescriptor* render_pass_descriptor = view.currentRenderPassDescriptor;
    render_pass_descriptor.colorAttachments[0].loadAction=MTLLoadActionClear;
    render_pass_descriptor.colorAttachments[0].storeAction=MTLStoreActionStore;
    render_pass_descriptor.colorAttachments[0].clearColor=MTLClearColorMake(1.0, 0.0, 0.0, 1.0);
    if(render_pass_descriptor != nil)
    {
        _uniform_buffer_index = (_uniform_buffer_index + 1) % MaxBuffersInFlight;
        //パラメータの更新＜新規＞
        uint32_t  uniform_buffer_offset = kAlignedUniformsSize * _uniform_buffer_index;
        void*     uniform_buffer_address = ((uint8_t*)_dynamic_uniform_buffer.contents) + uniform_buffer_offset;
        Uniforms * uniforms = (Uniforms*)uniform_buffer_address;
        uniforms->color[0]  = 0.0f;    //  R   色指定
        uniforms->color[1]  = 0.0f;    //  G
        uniforms->color[2]  = 1.0f;    //  B

        id <MTLRenderCommandEncoder> render_encoder = [command_buffer renderCommandEncoderWithDescriptor:render_pass_descriptor];
        //パラメータの設定＜新規＞
        [render_encoder setFragmentBuffer:_dynamic_uniform_buffer
                                   offset:uniform_buffer_offset
                                  atIndex:BufferIndexUniforms];
        //モデルの設定
        [render_encoder setRenderPipelineState:_pipeline_state];
        [render_encoder setVertexBuffer:_buffer_position 
                                 offset:0 
                                atIndex:BufferIndexPositions];
        [render_encoder drawPrimitives:MTLPrimitiveTypeTriangle 
                           vertexStart:0 
                           vertexCount:3];

        [render_encoder endEncoding];
        [command_buffer presentDrawable:view.currentDrawable];
    }
    [command_buffer commit];
}

- (void)mtkView:(nonnull MTKView *)view drawableSizeWillChange:(CGSize)size
{
}

@end
```
■Setp3：実行
---
iOsは実機で実行してくださいMacの場合は以下のようになります。
![スクリーンショット 2018-11-26 19.56.26.png](https://qiita-image-store.s3.amazonaws.com/0/122121/e5b62b54-b8f5-5921-7f3e-61e2fe7883f6.png)