30Days of Metal - 3.Commands

Martin Kim·2022년 12월 7일

30DaysOfMetal

목록 보기
3/3

Command, 명령

  • Metal에는 데이터를 제공하고 도형을 그릴 수 있는 메서드나 API가 존재하지 않음

  • 데이터 제공 및 그리기 명령의 실행은 별도의 작업이며 가장 일반적인 유형의 Metal 명령

  • 예시: 몇 개의 점 데이터가 포함된 버퍼가 있고, 선분을 그리려고 한다고 가정

    1. 먼저, 어떤 버퍼가 선 데이터를 보유하고 있는지 Metal에 알려야 함.

    2. 그리고 나서 Metal에게 선을 그리라고 지시함.

      // 의사코드
      commandList.setBuffer(pointBuffer)
      commandList.drawLines(1)
    • 이는 실제 Metal 코드는 아니지만, 일반적인 패턴임

Queuing Up

  • GPU는 CPU와는 분리된 프로세서

  • 우리는 둘 중 한 프로세서가 실행 중일 때, 다른 프로세서가 대기하는 상태 보다는, 2가지 프로세서가 동시에 작업이 실행되도록 하는 상태를 원함

  • 이러한 이유로 GPU는 지정한 명령은 즉시 실행되지 않음. 대신 명령은 명령 버퍼 (Command Buffer)에 수집된 다음 배치 실행 형태로 GPU에 전달됨.

  • Command Queue라는 객체를 사용하여 GPU에 명령을 전달

  • 다음 코드에서 commandQueue 객체는 MTLCommandQueue 프로토콜을 준수하는 객체임.

  • 명령 큐의 목적은 명령으로 채울 수 있는 명령 버퍼를 생성하고, 실행할 준비가 되었을 때 명령을 GPU로 전달하는 것

  • device에서 명령 대기열을 만들 수 있다.

let commandQueue = device.makeCommandQueue()!
  • device와 마찬가지로, 이 명령 큐는 앱이 실행되는 동안 활성 상태를 유지
  • 대부분의 경우, 따라서 1개만 만들어도 충분함

Command Buffer 생성 및 제출

  • 명령 큐를 생성했다면, 해당 큐에서 명령 버퍼를 생성
let commandBuffer = commandQueue.makeCommandBuffer()!
  • 버퍼를 통해 "버퍼 사용", "선 그리기", "데이터 복사" 등의 명령을 수행할 수 있게 됨
  • completion handler를 통해 버퍼가 완료될 때를 추적할 수 있음
commandBuffer.addCompletedHandler { completeCommandBuffer in
	print("Command Buffer completed")
}
  • command 버퍼를 실제 실행하기 위해서는, commit하여 연결된 commandQueue에 실행준비가 되었음을 알림
commandBuffer.commit()

Encoding Commands

  • command 버퍼 인코딩에 명령을 내리는 프로세스 호출
  • command encoding의 가장 쉬운 방법은 blit encoder를 사용하는 방법
  • blit encoder의 주요 목적은 리소스(즉, 버퍼 및 텍스처)간의 메모리 영역을 효율적으로 복사하는 것
  • 예시: 선분 포인트 데이터를 가진 버퍼로부터 다른 버퍼로 데이터를 복사하기
let sourceBuffer = device.makeBuffer(length: 16, options: [])!
let destBuffer = device.makeBuffer(length: 16, options: [])!
  1. 이전과 마찬가지로 sourceBuffer에 SIMD2<Float>타입을 binding하고 해당 요소를 설정하여 source buffer에 점 데이터를 넣기
let points = sourceBuffer.contents().bindMemory(to: SIMD2<Float>.self,
                                                capacity: 2)
points[0] = SIMD2<Float>(10, 10)
points[1] = SIMD2<Float>(100, 100)
  1. 버퍼간 데이터를 복사하기 위해 blit 명령 인코더 사용
  • device, command queue, command buffer가 이미 준비되었다면, command buffer로부터 blit encoder를 생성할 수 있다.
    let blitCommandEncoder = commandBuffer.makeBlitCommandEncoder()!
  • 각 command encoder에는 각 종류의 작업을 인코딩하는 메서드가 존재
    • compute command encoder: GPU의 일반적인 연산을 encode하는 command encoder
    • render command encoder: rendering command를 encode하는 command encoder
    • blit command encoder: 복사하는 명령을 encode하는 command encoder
  1. blit command encoder를 사용해 한 버퍼에서 다른 버퍼로 데이터를 복사
    blitCommandEncoder.copy(from: sourceBuffer,
                             sourceOffset: 0,
                             to: destBuffer,
                             destinationOffset: 0,
                             size: MemoryLayout<SIMD2<Float>>.stride * 2)
  • sourceBuffer와 destBuffer간 데이터를 완전히 복제하려 하므로, 두 오프셋은 0이고, 크기는 16바이트가 됨
  • 위 메서드는 copy command를 encoder의 관련 command buffer에 기록함
  • 복사는 즉시 발생하지 않고, buffer가 commit된 이후에 발생
  1. command encoder로 command 작성을 마치면 endEncoding() 호출
blitCommandEncoder.endEncoding()
  1. command 명령이 실제로 작동하는지 확인하기 위해 completion handler를 통해 데이터 출력
commandBuffer.addCompletedHandler { completeCommandBuffer in
    let outPoints = destBuffer.contents().bindMemory(to: SIMD2<Float>.self, capacity: 2)
    let p1 = outPoints[1]
    print("destBuffer's p1 is \(p1)")
}
// destBuffer's p1 is SIMD2<Float>(100.0, 100.0) 

출처: https://medium.com/@warrenm/thirty-days-of-metal-day-3-commands-775e5bd01438

profile
학생입니다

0개의 댓글