Metalのシェーダー

metal-shader.png

シェーダに頂点データを渡すには?

//float配列を定義
float rectData[8]={
    0,0,
    0,1,
    1,0,
    1,1
};
id <MTLBuffer> _vertexBufferRect;

MTLBufferにfloat配列データを入れる
_vertexBufferRect = [_device newBufferWithBytes:rectData length:sizeof(rectData) options:MTLResourceOptionCPUCacheModeDefault];
_vertexBufferRect.label = @"VerticesRect";//<-これはデバッグの時に使うラベルです

シェーダにMTLBufferを渡す
// Set context state
    [renderEncoder pushDebugGroup:@"DrawCube"];
    [renderEncoder setRenderPipelineState:_pipelineState];
    [renderEncoder setVertexBuffer:_vertexBuffer offset:0 atIndex:0 ];//シェーダにMTLBufferを渡す
    [renderEncoder setVertexBuffer:_dynamicConstantBuffer offset:(sizeof(uniforms_t) * _constantDataBufferIndex) atIndex:1 ];

シェーダのメイン関数と.swift or Objective-Cを接続するところ

.metal

Objective-Cの場合

// get the vertex function from the library
    id <MTLFunction> vertexProgram = [_defaultLibrary newFunctionWithName:@"lighting_vertex"];
    if(!vertexProgram)
        NSLog(@">> ERROR: Couldn't load vertex function from default library");

newFunctionWithNameのすぐ右がシェーダのメイン関数名
Swiftの場合
let vertexProgram = defaultLibrary?.newFunctionWithName("lighting_vertex")
let pipelineStateDescriptor = MTLRenderPipelineDescriptor()
pipelineStateDescriptor.vertexFunction = vertexProgram
// vertex shader function
vertex ColorInOut lighting_vertex(device vertex_t* vertex_array [[ buffer(0) ]],
                                  constant AAPL::constants_t& constants [[ buffer(1) ]],
                                  unsigned int vid [[ vertex_id ]])
{
    ColorInOut out;
 
    float4 in_position = float4(float3(vertex_array[vid].position), 1.0);
    out.position = constants.modelview_projection_matrix * in_position;
 
    float3 normal = vertex_array[vid].normal;
    float4 eye_normal = normalize(constants.normal_matrix * float4(normal, 0.0));
    float n_dot_l = dot(eye_normal.rgb, normalize(light_position));
    n_dot_l = fmax(0.0, n_dot_l);
 
    out.color = half4(constants.ambient_color + constants.diffuse_color * n_dot_l);
 
    return out;
}

頂点データを渡す

Metalのbuffer(0)buffer(1)と書いてあるところ
は、

// vertex shader function
vertex ColorInOut lighting_vertex(device vertex_t* vertex_array [[ buffer(0) ]],
                                  constant AAPL::constants_t& constants [[ buffer(1) ]],
                                  unsigned int vid [[ vertex_id ]])

MTLRenderCommandEncodersetVertexBuffer関数の最後の引数(何番目のVertexBufferか)に対応しています
let renderEncoder = commandBuffer.renderCommandEncoderWithDescriptor(renderPassDescriptor)!
        renderEncoder.label = "render encoder"
 
        renderEncoder.pushDebugGroup("draw morphing triangle")
        renderEncoder.setRenderPipelineState(pipelineState)
        renderEncoder.setVertexBuffer(vertexBuffer, offset: 256*bufferIndex, atIndex: 0)//Metalのbuffer(0)に対応
        renderEncoder.setVertexBuffer(vertexColorBuffer, offset:0 , atIndex: 1)//Metalのbuffer(1)に対応

シェーダに情報を送るものまとめ

MTLRenderCommandEncoderというクラスはシェーダーに渡すパラメータを管理するクラスの様子
修飾子 MTLRenderCommandEncoder
[[ vertex_id ]]
[[ stage_in ]] 任意の構造体に使う
[[ attribute_id ]]
[[ buffer(n) ]] setVertexBuffer() setFragmentBuffer()
[[ texture(n) ]] setVertexTexture() setFragmentTexture()
[[ sampler(n) ]] setVertexSamplerState() setFragmentSamplerState()
[[ threadgroup(n) ]]

アドレス空間修飾子 

Metalではアドレス空間修飾子というものがあって、
どのメモリ領域で変数割り当てをするのかを指定することができます。

// vertex shader function
vertex ColorInOut lighting_vertex(device vertex_t* vertex_array [[ buffer(0) ]],//device修飾子 アドレス空間がデバイス 読み書き可能
                                  constant AAPL::constants_t& constants [[ buffer(1) ]],//constant修飾子 このシェーダを使ってる間は不変
                                  unsigned int vid [[ vertex_id ]])//アドレス空間修飾子なし

device アドレス空間がデバイス 読み書き可能 描画関数,カーネル関数
constant 読み込み可能、書き込み禁止のメモリ デバイスメモリプールから割り当てられている そのシェーダが走ってる間は不変 描画関数,カーネル関数
threadgroup カーネル関数で使用される変数 このスレッドグループに属するすべてのスレッドからアクセスされる。この修飾子のついたものは描画関数から使用することはできない カーネル関数
thread 1スレッドにつき、1個の変数 カーネル関数

どんな型のものでもシェーダに送れる。ただし一回MTLBuffer型に変換しないといけない。

MTLBuffer型に変換するための関数

MTLDeviceの関数でMTLBufferオブジェクトを作れます

newBufferWithLength(データ,options) 新しい空のMTLBuffer を作ります。メモリ領域は割り当てます
newBufferWithBytes(データ,length,options) 既に存在している配列や構造体をコピーしてMTLBufferを作ります。渡す配列やっ構造体はCPU側領域に存在しているものです。それをGPU領域に同じサイズだけ割り当てます。
newBufferWithBytesNoCopy(データ,length,options,deallocator) 既に存在している配列や構造体をコピーしてMTLBufferを作ります。and does not allocate any new storage for this object.

サポートサイト Wikidot.com