티스토리 뷰
Metal을 공부하면서 정리한 내용입니다.
Metal은 GPU에 접근할 수 있는 기능을 제공하여 빠른 그래픽 처리를 가능하게 하는 API입니다.
OpenGL ES와 유사하지만 크로스 플랫폼이 아니라는 차이가 있고, 속도나 오버헤드 측면에서 Metal이 더 좋은 성능을 가지고 있습니다.
SpriteKit, SceneKit, Unity and Metal
Metal은 OpenGL ES와 비슷한 저수준 3D 그래픽 API로 GPU 위에 있는 레이어입니다.
2D, 3D 모델을 렌더링 하려면 모든 작업에 대한 코드를 작성해야 합니다.
반대로 SpriteKit, SceneKit, Unity는 상위 레벨 프레임워크로 Metal, OpenGL ES와 같은 하위 레벨 그래픽 API 위에 구축이 되어 있습니다.
Metal 및 OpenGL ES 기반으로 미리 작성된 boilerplate 코드를 제공해 줌으로써 더 쉽게 렌더링 작업이 가능합니다.
기본 예제인 삼각형 그리기를 Metal을 이용하여 그리는 코드를 단계별로 정리해 봤습니다.
Rendering Process
Metal은 대부분의 렌더링 구성 요소를 객체로 구성합니다.
예를 들어, 렌더링 파이프 라인은 Pipeline 객체로 구현하고 쉐이더는 라이브러리 객체, vertex 데이터는 Buffer 객체로 캡슐화됩니다.
Metal의 렌더링 프로세스는 렌더링 전에 필요한 객체를 초기화하고, 이 객체들을 쉐이더로 전달합니다.
렌더링 프로세스를 아래의 순서대로 나열해 보았습니다.
렌더링 전에 초기화할 객체
초기화 단계에서 생성된 객체는 애플리케이션의 수명 주기와 같습니다.
- MTLDevice 객체 생성
- MTLCommandQueue 객체 생성
- CAMetalLayer 객체 생성
- MTLLibrary, MTLFunction 객체 생성
- MTLRenderPipelineState 객체 생성
- MTLBuffer 객체 생성
렌더 패스(쉐이더와 연결)
렌더 단계에서 생성된 객체는 자주 생성되어 수명이 짧습니다.
- Drawable 객체 얻기
- MTLRenderPassDescriptor 객체 생성
- MTLCommandBuffer 및 MTLRenderCommandEncoder 객체 생성
- Drawable 등록
- CommandBuffer 커밋
1. MTLDevice 객체 생성
MTLDevice 객체는 Buffer, Library, Pipeline과 같은 여러 종류의 객체를 만드는 데 사용됩니다.
let device = MTLCreateSystemDefaultDevice()
2. MTLCommandQueue 객체 생성
MTLCommandQueue 객체는 명령을 GPU에 보내는 방법을 제공합니다.
let commandQueue = device.makeCommandQueue()
3. CAMetalLayer 객체 생성
CAMetalLayer 객체는 렌더링 할 텍스처를 출력할 대상입니다.
let metalLayer = CAMetalLayer()
metalLayer.device = self.device
metalLayer.pixelFormat = .bgra8Unorm
metalLayer.framebufferOnly = true
metalLayer.frame = view.layer.frame
view.layer.addSublayer(metalLayer)
4. MTLLibrary, MTLFunction 객체 생성
MTLLibrary 객체를 통해 renderPipeline에서 사용할 vertex, fragment 함수(쉐이더)를 정의합니다.
let library = device.makeDefaultLibrary()
let vertexFunction = library?.makeFunction(name: "vertexPassThrough")
let fragmentFunction = library?.makeFunction(name: "fragmentPassThrough")
5. MTLRenderPipelineState 객체 생성
MTLRenderPipelineState 객체는 렌더링 파이프 라인을 나타냅니다.
MTLRenderPipelineState는 MTLRenderPipelineDescriptor 객체를 통해 생성합니다.
MTLRenderPipelineDescriptor는 MTLRenderPipelineState의 속성을 설명합니다.
MTLRenderPipelineDescriptor에서는 vertex, fragment 함수 및 컬러의 첨부 속성 등을 설정합니다.
let pipelineDescriptor = MTLRenderPipelineDescriptor()
pipelineDescriptor.vertexFunction = vertexFunction
pipelineDescriptor.fragmentFunction = fragmentFunction
pipelineDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm
let pipelineState = try! device.makeRenderPipelineState(descriptor: pipelineDescriptor)
6. MTLBuffer 객체 생성
vertex(꼭짓점) 데이터를 구성하여 MTLBuffer 객체를 생성합니다.
버퍼를 만들 때, 버퍼에 얼마나 많은 데이터를 저장할지 모르기 때문에 vertex 배열의 크기를 계산하여 공간을 할당해야 합니다.
let vertexData: [Float] = [
0, 1,
-1, -1,
1, -1
]
let vertexDataSize = vertexData.count * MemoryLayout<Float>.size
let vertexBuffer = device.makeBuffer(bytes: vertexData,
length: vertexDataSize,
options: [])
7. Drawable 객체 얻기
렌더링 결과를 화면에 출력하려면 텍스처가 필요하기 때문에 CAMetalLayer 객체에 사용 가능한 텍스처를 요청합니다.
let drawable = metalLayer.nextDrawable()
8. MTLRenderPassDescriptor 객체 생성
MTLRenderPassDescriptor에 CAMetalLayer가 제공하는 텍스처를 연결합니다.
연결한 텍스처에 추가적으로 작업을 하고 싶다면 원하는 옵션에 값을 설정할 수 있습니다.
아래는 색 데이터를 설정하는 코드입니다.
colorAttachments는 렌더링 된 이미지의 색 데이터가 저장되어 있는 곳이라고 생각하면 됩니다.
let renderPassDescriptor = MTLRenderPassDescriptor()
renderPassDescriptor.colorAttachments[0].texture = drawable.texture
renderPassDescriptor.colorAttachments[0].loadAction = .clear
renderPassDescriptor.colorAttachments[0].clearColor = .init(red: 0, green: 0, blue: 0, alpha: 1)
9. MTLCommandBuffer 및 MTLRenderCommandEncoder 객체 생성
MTLCommandBuffer 객체는 커밋되기 전까지 렌더링 파이프 라인 상태를 저장합니다.
MTLCommandBuffer 객체에 렌더링 파이프 라인 상태 저장하기 전에 MTLRenderCommandEncoder 객체를 생성하여 인코딩해야 합니다. 인코딩 작업에서는 명령을 GPU가 이해할 수 있는 형식으로 변환해 줍니다.
let commandBuffer = commandQueue.makeCommandBuffer()
let commandEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor)
commandEncoder?.setRenderPipelineState(pipelineState)
commandEncoder?.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
commandEncoder?.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: vertexData.count)
commandEncoder?.endEncoding()
위 코드에서 drawPrimitives 함수의 타입은 꼭짓점을 어떻게 연결한 것 인가에 대한 중요한 설정 값입니다.
vertex 좌표 값과 타입을 같이 변경하면서 어떻게 그려지는지 보면 쉽게 알 수 있을 것입니다.
10. Drawable 등록
렌더링이 완료되면 텍스처를 화면에 출력해야 하는데, 렌더링 될 대상인 CAMetalLayer에서 가져온 Drawable 객체를 버퍼에 등록해 줘야 합니다.
commandBuffer.present(drawable)
11. CommandBuffer 커밋
마지막으로 버퍼가 커밋되어 명령 큐에 로드되어 GPU에 의해 실행됩니다.
commandBuffer.commit()
여기까지 렌더링을 위한 초기화 및 렌더 패스 작업이 끝났습니다.
마지막으로 쉐이더 함수를 만들어 주면 됩니다.
쉐이더 함수 생성
GPU에서 실행되는 처리는 MSL(Metal Shading Language)라는 언어를 사용합니다.
이제 MSL를 사용하여 간단한 쉐이더(vertex, fragment 함수)를 만들어 보겠습니다.
Metal 파일을 새로 생성합니다.
위에서 만든 MTLFunction 객체의 이름과 동일한 vertex 및 fragment 함수를 생성합니다.
vertex float4 vertexPassThrough(const device packed_float2* vertices [[ buffer(0) ]],
unsigned int vertexId [[ vertex_id ]])
{
return float4(vertices[vertexId], 0.0, 1.0);
}
vertex 함수의 주요 기능은 전달받은 vertex 및 텍스처 좌표 값을 변환해 줍니다.
vertices 파라미터는 위에서 MTLRenderCommandEncoder 객체에 추가한 vertextBuffer의 꼭짓점 데이터가 반환된 값입니다.
fragment half4 fragmentPassThrough() {
return half4(0, 1, 0, 1);
}
fragment 함수의 주요 기능은 픽셀의 색상을 결정합니다.
이렇게 만든 함수는 컴파일되어 라이브러리에 저장되고, 파이프라인에서 사용되게 됩니다.
여기까지가 끝입니다!! 👏👏👏
프로젝트를 실행하면 초록색의 삼각형이 화면에 나타날 것입니다!!
Metal은 시뮬레이터에서 지원되지 않기 때문에 Device로 실행해야 합니다.
다음에는 MSL(Metal Shading Language)에 대해 좀 더 자세히 정리해 보겠습니다! 👨🏻💻👨🏻💻👨🏻💻
Reference
https://www.haroldserrano.com/blog/getting-started-with-metal-api
https://www.raywenderlich.com/7475-metal-tutorial-getting-started
https://www.raywenderlich.com/5493-metal-rendering-pipeline-tutorial
'Swift > Metal' 카테고리의 다른 글
[Metal] MSL(Metal Shading Language)에 대하여 (1) | 2020.05.21 |
---|
- Total
- Today
- Yesterday
- carousel
- xib
- Video
- m3u8
- TDD
- RECORDING
- IOS
- Design Pattern
- Swift
- UIButton
- http live streaming
- HLS
- CollectionView
- BaseViewController
- Coordinator
- permission error
- database
- ssh
- AssociatedObject
- testing
- Closure
- AVFoundation
- NIB
- pagingView
- Cleancode
- Realm
- AVKit
- customAlertView
- UIBarButtonItem
- UIControl
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |