Projects/Metal - Document

Metal-cpp 02 : Store Shader Arguments in a Buffer

아헿헿헿 2022. 8. 7. 19:04

참조 : https://developer.apple.com/documentation/metal/using_a_render_pipeline_to_render_primitives?language=objc 

https://developer.apple.com/documentation/metal/buffers/managing_groups_of_resources_with_argument_buffers?language=objc 

 

 

이번 장은 이전 장에서 argument buffer를 추가하여 Vertex shader에 버퍼를 간접적으로 제공합니다. Argument Buffer는 다른 버퍼 및 Metal 리소스 개체에 대한 참조를 포함하는 특정 유형의 버퍼입니다. 이러한 Argument buffer는 CPU 오버헤드를 줄이고 자원 관리를 간단화시킵니다. 이는 CPU에서 수행하는 것보다 GPU에서 작업을 더 많이 수행하며, CPU 명령의 반복을 줄여줍니다. 즉, 3D mesh 및 텍스처들을 하나하나 GPU에 이동시키는 것이 아닌 전체 틀을 가지고 있는 argument buffer를 통해 한번에 이동시켜 CPU에서의 작업량을 줄일 수 있도록 합니다.  밑의 그림에서 위가 argument buffer를 쓰기 전으로 하나하나 설정해주고 있다면, argument buffer를 통해 아래 처럼 한번에 설정 가능하게 된다는 것입니다.

 

 

 

따라서 이전의 코드에서 buildBuffers() 메서드를 확장하며 정점 위치와 색상 버퍼에 대한 참조를 포함하는 argument buffer를 빌드하고자 합니다.

[1] Base Setup

Renderer는 먼저 셰이더의 매개 변수 중 하나에서 argument encoder를 생성해야 합니다. 이는 셰이더 함수의 newArgumentEncoder() 함수에 인코딩하고자 하는 버퍼 매개 변수의 인덱스로 호출함으로써 수행됩니다.

MTL::Function* pVertexFn = _pShaderLibrary->newFunction( NS::String::string( "vertexMain", UTF8StringEncoding ) );
MTL::ArgumentEncoder* pArgEncoder = pVertexFn->newArgumentEncoder( 0 );

MTL::Buffer* pArgBuffer = _pDevice->newBuffer( pArgEncoder->encodedLength(), MTL::ResourceStorageModeManaged );
_pArgBuffer = pArgBuffer;

pArgEncoder->setArgumentBuffer( _pArgBuffer, 0 );

pArgEncoder->setBuffer( _pVertexPositionsBuffer, 0, 0 );
pArgEncoder->setBuffer( _pVertexColorsBuffer, 0, 1 );

그 후, 인코더 객체가 파라미터의 타입에 따라 메모리 요구사항을 해석하는데, 이 때 이는 encodedLengh()로 받아올 수 있습니다. 이 크기로 Argument Buffer를 저장할 공간을 할당하고, 인코더에 setArgumentBuffer를 통해 이를 binding합니다. 그 후, 위치 및 색 버퍼를 setBuffer()를 통해 참조하도록 합니다.

 

 

[2] Vertex Shader

위와 같이 Argument Buffer를 설정한 경우, 아래와 같이 [[id(n)]]를 통해 지정한 위치의 버퍼를 가져올 수 있습니다.

struct VertexData
{
    device float3* positions [[id(0)]];
    device float3* colors [[id(1)]];
};

v2f vertex vertexMain( device const VertexData* vertexData [[buffer(0)]], uint vertexId [[vertex_id]] )
{
    v2f o;
    o.position = float4( vertexData->positions[ vertexId ], 1.0 );
    o.color = half3(vertexData->colors[ vertexId ]);
    return o;
}

 

 

[3] Draw

셰이더가 argument buffer를 통해 이러한 정점 데이터 버퍼를 간접적으로 참조하기 때문에 렌더러는 useResource() 메서드를 호출해야 합니다. 이것은 명령 버퍼를 실행할 때 버퍼가 메모리에 있어야 함을 Metal에 나타냅니다.

void Renderer::draw( MTK::View* pView )
{
    NS::AutoreleasePool* pPool = NS::AutoreleasePool::alloc()->init();

    MTL::CommandBuffer* pCmd = _pCommandQueue->commandBuffer();
    MTL::RenderPassDescriptor* pRpd = pView->currentRenderPassDescriptor();
    MTL::RenderCommandEncoder* pEnc = pCmd->renderCommandEncoder( pRpd );

    pEnc->setRenderPipelineState( _pPSO );
    pEnc->setVertexBuffer( _pArgBuffer, 0, 0 );
    pEnc->useResource( _pVertexPositionsBuffer, MTL::ResourceUsageRead );
    pEnc->useResource( _pVertexColorsBuffer, MTL::ResourceUsageRead );
    pEnc->drawPrimitives( MTL::PrimitiveType::PrimitiveTypeTriangle, NS::UInteger(0), NS::UInteger(3) );

    pEnc->endEncoding();
    pCmd->presentDrawable( pView->currentDrawable() );
    pCmd->commit();

    pPool->release();
}