Hello, Metal
카테고리: Graphics
**Main Reference
- Metal by Tutorials - 4th edition
Initialize Metal ➡️ Load a model ➡️ Set up the pipeline ➡️ Render
Initialize Metal
import MetalKit
guard let device = MTLCreateSystemDefaultDevice() else {
fatalError("GPU is not supported")
}
let frame = CGRect(x: 0, y: 0, width: 600, height: 600)
let view = MTKView(frame: frame, device: device)
view.clearColor = MTLClearColor(red: 1, green: 1, blue: 0.8, alpha: 1)
먼저 Metal을 쉽게 사용할 수 있게 해주는 프레임워크인 MetalKit을 임포트한다. MetalKit에서 제공하는 MTKView나 Model I/O는 텍스처나 버퍼들을 간편하게 로딩하고 사용할 수 있다. 특히 Renderer에 사용되는 MTKView는 매 프레임 동작하는 delegate method를 가지고 있다. macOS에서는 NSView의 서브클래스이고, IOS에서는 UIView의 서브클래스이다.
Load a Model
let allocator = MTKMeshBufferAllocator(device: device)
let mdlMesh = MDLMesh(
sphereWithExtent: [0.75, 0.75, 0.75],
segments: [100, 100],
inwardNormals: false,
geometryType: .triangles,
allocator: allocator)
let mesh = try MTKMesh(mesh: mdlMesh, device: device)
Model I/O 프레임워크는 블렌더나 마야에서 생성한 3D 모델들을 불러올 수 있게 해준다. 또한 간단한 도형같은 경우 직접 생성할 수 있다.
- allocator : mesh data 메모리 관리
- CPU/GPU 메모리 할당 결정, attribute배치/정렬 등 최적화
- MDLMesh : Metal뿐 아니라 다른 API에서도 사용 가능한 높은 수준의 추상화 객체
- 애초에 다른 플랫폼에서 생성한 메시를 로드하는 역할
- MTKMesh : Metal 렌더링에 특화된 데이터
Set up the Pipeline
각 프레임은 render command encoder에 래핑되어 전달된 명령어들로 이루어진다. Command buffer는 command encoder들로 이루어져 있고, command queue는 command buffer로 이루어져 있다.
일반적으로 하나의 프로그램은 동일한 Device와 Command queue를 사용한다. 렌더 패스가 효율적으로 작동하기 위해 한 번만 초기화 하면 되는 객체들은 앱 시작 시 미리 로드한다. 매 프레임 command buffer를 생성해야 하고, 렌더 패스를 묘사하는 최소 하나의 command encoder가 필요하다.
Render command encoder는 GPU의 pipeline state를 지정하고, 렌더 패스 동안 어떤 버퍼들을 사용할지 그리고 어떤 shader들을 사용하는지 알려주는 가벼운 객체이다. 이때 pipeline state나 buffer, shader들은 앱 시작시 미리 로드한다.
let pipelineDescriptor = MTLRenderPipelineDescriptor() pipelineDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm pipelineDescriptor.vertexFunction = vertexFunction pipelineDescriptor.fragmentFunction = fragmentFunction
// Model I/O로 생성한 메시는 vertex descriptor도 제공
pipelineDescriptor.vertexDescriptor = MTKMetalVertexDescriptorFromModelIO(mesh.vertexDescriptor)`
let pipelineState = try device.makeRenderPipelineState(descriptor: pipelineDescriptor)
Metal에서는 GPU에게 Pipeline State(어떻게 그릴 것인지)를 알려줘야 한다. Pipeline state는 한 번 세팅하고 나면 바꾸기 전까지 state가 고정된다. 따라서 state 변경이 필요한 경우, 최소한의 변경을 하는 것이 효율적이다. Pipeline State에는 pixelFormat, Shaders, vertexDescriptor, … 등 여러 정보가 필요한데, 직접 생성하지는 않고 descriptor를 이용해 생성한다.
Pipeline state를 생성하는 작업은 유의미한 시간을 필요로 한다. 따라서 생성은 한 번만 하고, encoder로 전달해주는 작업만 매 프레임 실행된다. 실제 앱에서는 별도의 shader를 사용하거나, vertex descriptor가 다른 경우들에 대하여 몇 개의 pipeline state를 생성해두고 사용한다.
Rendering
결국 GPU의 최종 작업은 3D Scene으로 부터 2D Texture를 얻는 것인데, 이는 physical camera와 유사하다. 여기에 2D Texture를 device screen에 띄우는 것 까지가 렌더링 작업이다.
guard let commandBuffer = commandQueue.makeCommandBuffer(),
let renderPassDescriptor = view.currentRenderPassDescriptor,
let renderEncoder = commandBuffer.makeRenderCommandEncoder( descriptor: renderPassDescriptor)
else { fatalError() }
Render Pass는 어디에 그릴 것인지를 의미한다. 사실적인 렌더링을 구현할 때 필요에 따라 여러 렌더 패스로 나누어 계산하기도 하는데, 각 render pass는 render pass descriptor가 필요하다. Render pass descriptor는 attachment라 불리는 render destination에 대한 정보를 들고있다. 예를 들어 어떤 texture에 그릴 것인지, 결과를 저장할 것인지, 초기화 할 것인지 등에 대한 정보이다. 이러한 render pass descriptor는 render command encoder를 생성활 때 사용된다. 참고로 MTKView의 경우 drawable을 통해 render texture를 가져올 수 있고, render pass descriptor 또한 간편하게 받아올 수 있다.
SubMeshes
guard let submesh = mesh.submeshes.first else { fatalError() }
renderEncoder.drawIndexedPrimitives( type: .triangle,
indexCount: submesh.indexCount,
indexType: submesh.indexType,
indexBuffer: submesh.indexBuffer.buffer,
indexBufferOffset: 0 )
renderEncoder.endEncoding()
guard let drawable = view.currentDrawable else { fatalError() }
commandBuffer.present(drawable)
commandBuffer.commit()
일반적으로 하나의 3D 모델은 여러 submesh들로 이루어져 있다. 하나의 vertex가 여러 submesh에 포함되는 경우도 존재한다. 3D Model을 렌더링 할 때에는 submesh 단위로 draw를 호출하며, 렌더링 작업을 마무리해주어야 한다.
- endEncoding : draw call 전부 날림
- drawable texture 가져오기 & present 지정(screen으로 띄울 texture)
- commit
[!important] Render Pipeline : 어떻게 그릴지 Render Pass : 어디에 그릴지
댓글 남기기