본문 바로가기
🍎/Metal

[Thirty Days of Metal] Day3-Commands

by venniek 2023. 1. 1.

원문: Thirty Days of Metal-Day 3: Commands

위 글을 공부하고 요약했습니다.

 

device를 만들고 GPU 메모리를 할당하는 것도 중요하지만, GPU에게 일을 시키려면 그것의 언어를 배워야 한다. 명령하는 법을 배워보자.

What are Commands?

Metal에는 데이터를 제공하고 모양을 그려내는 걸 동시에 하는 함수가 없고 둘은 구분된 동작이다.

두 점을 가진 버퍼가 있고 선 하나를 그린다고 해보자. 먼저 Metal에게 어떤 버퍼가 선 데이터를 가질지 알려준다. 그 후 선을 그리라고 해야 한다. 수도코드로 다음과 같다.

commandList.setBuffer(pointBuffer)
commandList.drawLines(1)

실제 코드는 아니지만 매우 일반적인 패턴이다. 실행할 GPU에 대한 정보를 제공하고, 하고싶은 동작을 특정한다.

 

Queuing Up

GPU는 우리가 지정한대로 즉시 명령을 실행하지 않고 command buffer에 명령이 쌓이고 덩어리로 GPU에 전달된다.

MTLCommandQueue프로토콜을 채택하는 command queue 객체를 사용해 GPU로 명령을 전달한다. 이 객체는 명령이 들어가는 명령 버퍼를 만들고 실행 준비가 되면 GPU로 전달한다.

devicemakeCommandQueue() 메서드를 이용한다.

let commandQueue = device.makeCommandQueue()!

device처럼, command queue는 앱을 실행하는 동안 살아있으므로 하나만 만들면 된다.

 

Creating and Submitting Command Buffers

큐에 GPU로 보낼 일을 주자. commandQueue로 명령 버퍼를 만들어 명령을 넣어야 한다.

commandQueuemakeCommandBuffer() 메서드를 이용한다.

let commandBuffer = commandQueue.makeCommandBuffer()!

이제 빈 명령 버퍼가 생겼다. GPU에게 빈 명령 버퍼를 실행하고 언제 끝나는지 알려달라고 하자. completed handler를 사용한다.

commandBuffer.addCompleteHandler { completedCommandBuffer in
                                  print("Command buffer completed")
                                 }

이렇게 완료 안내를 받는 것은 CPU와 GPU가 비동기적으로 최대한 효율적으로 독립되어 실행되게 해준다.

명령 버퍼를 실행하기 위해 command queue가 실행 준비가 됐다고 commit 한다.

commandBuffer.commit()

 

Encoding Commands

명령을 버퍼에 쓰는 과정을 encoding이라 한다. blit encoder는 리소스들(buffer, texture) 간에 메모리 영역을 효율적으로 복사한다.

점들의 버퍼에서 다른 버퍼로 데이터를 복사한다고 해보자. 출발과 도착 버퍼를 만드는 것으로 시작할 것이다.

let sourceBuffer = device.makeBuffer(length: 16, options: [])!
let destBuffer = device.makeBuffer(length: 16, options: [])!

SIMD2<Float> 배열을 가리키는 포인터를 만들고 원소를 할당해 출발 버퍼에 데이터를 쓴다.

let points = sourceBuffer.contents().bindMemory(to: SIMD2<Float>.self, capacity: 2)
points[0] = SIMD2<Float>(10, 10)
points[1] = SIMD2<Float>(100, 100)

버퍼끼리 데이터를 복사하려면 blit command encoder를 사용한다.

let blitCommandEncoder = commandBuffer.makeBlitCommandEncoder()!

다른 버퍼로 복사하기 위해 encodercopy(from:sourceOffset:to:destinationOffset:size:) 메서드를 사용한다.

blitCommandEncoder.copy(from: sourceBuffer, 
                        sourceOffset: 0,  
                        to: destBuffer,  
                        destinationOffset: 0,  
                        size: MemoryLayout<SIMD2<Float>>.stride * 2)

이 메서드는 encoder와 연결된 명령 버퍼에 복사 명령을 쓴다. 즉시 복사되지 않고 버퍼를 commit 하고 난 후 복사된다.

명령이 끝났으면 endEncoding() 메서드로 인코딩을 끝낸다.

blitCommandEncoder.endEncoding()

 

전체 코드, 출력 결과

import Metal

import Metal

let device = MTLCreateSystemDefaultDevice()!
let buffer = device.makeBuffer(length: 16, options: [])!

let commandQueue = device.makeCommandQueue()!
let commandBuffer = commandQueue.makeCommandBuffer()!

let sourceBuffer = device.makeBuffer(length: 16, options: [])!
let destBuffer = device.makeBuffer(length: 16, options: [])!

// 출발 버퍼에 값 할당
let points = sourceBuffer.contents().bindMemory(to: SIMD2.self, capacity: 2)
points[0] = SIMD2(10, 10)
points[1] = SIMD2(100, 100)

// 인코더 생성
let blitCommandEncoder = commandBuffer.makeBlitCommandEncoder()!
// 인코더로 도착 버퍼에 값 복사
blitCommandEncoder.copy(from: sourceBuffer,
                        sourceOffset: 0,
                        to: destBuffer,
                        destinationOffset: 0,
                        size: MemoryLayout<SIMD2>.stride * 2)
// 인코딩 끝내기
blitCommandEncoder.endEncoding()

// 버퍼 실행 종료 알림
commandBuffer.addCompletedHandler { completedCommandBuffer in
    let destPoints = destBuffer.contents().bindMemory(to: SIMD2.self, capacity: 2)
    print("p1 is \(points[0]) in source\n  and \(destPoints[0]) in destination")
    print("p2 is \(points[1]) in source\n  and \(destPoints[1]) in destination")
}

// 버퍼 commit
commandBuffer.commit()





 

'🍎 > Metal' 카테고리의 다른 글

[Thirty Days of Metal] Day2-Buffers  (0) 2022.12.31
[Thirty Days of Metal] Day1-Devices  (0) 2022.12.31

댓글