nyancoder

WWDC 2021 - Explore bindless rendering in Metal 본문

WWDC/WWDC 2021

WWDC 2021 - Explore bindless rendering in Metal

nyancoder 2021. 7. 16. 02:22

원본 영상: https://developer.apple.com/videos/play/wwdc2021/10286/

 

Motivation

  • 바인드를 하지 않는 렌더링을 사용하면 모든 리소스를 셰이더에서 사용할 수 있으므로 그래픽 처리를 유연하게 할 수 있습니다.

  • 예를 들어 광선과 모델의 교차점을 찾는 커널이 있다고 가정합니다.

  • Metal Shading Language와 빛과 물체 뒤의 교차점을 찾는 알고리즘을 사용하여 지면에 그림자가 생성되는 지점을 찾는다고 가정합시다.

  • 교차점을 찾은 후 단색으로 칠하면 지면이 단색으로 칠해지기 때문에 올바르게 보이지 않으므로 해당 픽셀의 음영 값을 정확하게 계산해야 합니다.
  • 하지만 광선을 추적할 때, 주면의 모든 물체에 부딪힐 수 있기 때문에 교차되는 메시와 관련된 정점 정보와 질감 정보를 다 접근해야 할 필요가 있습니다.
  • 그렇기 때문에 이 많은 양의 리소스를 파이프라인에 직접 바인딩하는 것은 불가능합니다.

 

Bindless binding model

  • 바인딩을 하지 않는 방법의 핵심은 리소스들을 함께 모아서 연결하는 것입니다.
    이를 통해 단일 버퍼로 파이프라인에 바인딩하여 참조되는 모든 리소스를 사용할 수 있습니다.
  • Metal에서 이를 가능하게 하는 것은 인수 버퍼(argument buffers)이며, bindless의 경우 Argument Buffers Tier 2가 필요합니다.
  • Apple6 및 Mac2 GPU 제품군에서 사용할 수 있습니다.
  • 인수 버퍼는 Metal의 모든 셰이더 유형에서 사용할 수 있습니다.
  • 광선 추적과 래스터화 모두에 사용할 수 있음을 의미합니다.
  • 래스터화의 경우는 바인딩을 하지 않으면 주어진 드로우 콜에 바인딩할 수 있는 리소스 수에 대한 제한을 제거할 수 있습니다.

  • 인수 버퍼는 한 번의 호출로 상수 데이터와 리소스를 한 번에 바인딩할 수 있는 방법이며, Metal 2와 함께 도입되었습니다.
  • 이 방법은 매우 유연하며 다른 버퍼를 참조할 수 있기 때문에 모든 리소스를 함께 한 번에 연결할 수 있습니다.

  • 위와 같은 소방차 모델을 렌더링 할 때는 모델을 구성하는 텍스쳐, 정점 데이터, 인덱스를 각 호출마다 하나씩 바인딩해야 합니다.

  • 하위 데이터나 다른 메시를 포함하는 인수 버퍼를 먼저 생성하면 정점 및 인덱스 데이터에 접근할 수 있습니다.
  • 동일하게 Material에 텍스쳐나 인라인 상수 데이터를 포함하여 구성합니다.
  • 그다음 각 인스턴스에서 Material과 메시를 참조합니다.
  • 이후 인스턴스를 여러 개 만든 다음 전체 장면을 인수 변수에 연결할 수 있습니다.
  • 이제 최상위 버퍼를 인수 버퍼에 전달하여 모든 리소스에 접근할 수 있습니다.

  • 장면에 대한 포인터만 파이프라인으로 전달하기 때문에 Metal은 직접 적으로 참조되는 버퍼만 알 수 있고 간접적으로 액세스 되는 리소스에 대해서는 알 수 없습니다.
  • 따라서 간접적으로 액세스 되는 모든 리소스가 메모리에 상주되어있도록 해야 할 책임이 사용자에게 있습니다.
  • 컴퓨팅 인코더의 경우 useResource:usage: 를 호출하고 렌더 명령 인코더의 경우 useResource:usage:stages: 를 호출하면 됩니다.
  • 상주되지 않은 리소스에 접근하는 것은 것은 GPU가 재시작되는 일반적인 원인입니다.

  • 편의를 위해서 MTL Heap에서 할당된 리소스를 useHeap API를 통해 한번에 불러올 수 있습니다.
  • 그렇기 때문에 성능과 메모리 절약을 위해 힙을 사용하는 것이 좋습니다.

  • 이때, 대상 리소스가 읽기 전용인지 유무가 중요합니다.
  • 만일 컴퓨팅 셰이더나 동적 텍스쳐의 메시 스킹과 같은 쓰기 작업이 필요한 리소스의 경우는 쓰기 플래그를 사용하여 개별적으로 처리해야 합니다.
  • 또한 수정되었을 가능성이 있는 리소스는 Metal이 GPU캐시를 업데이트하도록 하기 위해 useResource 호출이 필요합니다.

  • 두 번째로 리소스가 추적이 가능한지가 중요한 요소입니다.

  • 한 Heap내의 서로 다른 리소스에 읽기와 쓰기를 처리하면, 병렬로 처리될 수 있는 일이라도 Heap이 단일 리소스기 때문에 순서대로 처리해야 한다고 판단할 수 있기 때문입니다.

  • 첫 번째 방법은 동시에 처리될 가능성이 있는 리소스는 서로 다른 Heap으로 분리하여 할당하는 것입니다.

  • 다른 방법은 하위 할당 리소스를 추적하지 않도록 구성하는 것입니다.
  • 이 방법이 Metal에서 기본적인 방법으로 사용자가 직접 관리의 책임을 가집니다.
  • 이때에는 스테이지 단위로 fence를 지정하여 최소한의 단계만 차단되도록 할 수 있습니다.

  • 가장 중요한 요소는 정적 텍스처 및 메시와 같은 읽기 전용 데이터는 처리하기 가장 쉽다는 점입니다.
  • 이러한 경우 리소스를 힙에 배치하기만 하면 최소한의 오버헤드로 단일 호출로 처리할 수 있습니다.

 

Argument Buffer encoding

  • 배열로 구성되어 있는 인스턴스 버퍼를 인코딩하는 예를 보겠습니다.
  • 이 예에서는 인스턴스는 재질인 메시와 참조하고 로컬 공간에서 월드 공간으로의 변환을 설명하는 인라인 상수 4x4 행렬을 포함합니다.

  • 리플렉션을 이용한 인코딩을 하면 인수 버퍼가 셰이더 함수에 전달되어 MTLFunction객체가 인코더를 생성하도록 요청할 수 있습니다.
  • 하지만 이 방법으로는 간접적으로 참조되는 버퍼에 대해서 알 수 없고, 함수가 배열을 전달할 때에는 사용할 수 없습니다.
  • 또한 파이프라인 상태 생성과 별도로 인수 버퍼를 생성하는 경우에는 이 방법이 어려울 수 있습니다.

  • MTLArgumentDescriptor를 사용해 구조체의 멤버들을 전달하는 방법으로 인코더를 생성할 수 있습니다.
  • 이 방법을 위해서는 데이터 유형과 바인딩 인덱스를 지정하여 각 멤버에 대한 설명자를 만든 다음, 이를 MTLDevice에 전달해야 합니다.

  • 이때에는 각 멤버에 대해서 MTLArgumentDescriptor를 만들고 멤버에 대한 바인딩 인덱스와 MTLDataType을 지정해야 합니다.
  • 모든 멤버에 대해서 정의가 끝나면, 이를 배열을 통해서 전달하여 인코더를 만들 수 있습니다.

  • 이 방법을 통하면 각 데이터의 오프셋에 encodingLength를 곱한 위치에 버퍼를 기록하면 됩니다.
  • 이때 쉐이더에서는 각 데이터의 인덱스만 알고 있으면 쉽게 접근할 수 있습니다.

 

Buffer Navigation

  • 바인드를 하지 않는 것은 레이 트레이싱에 잘 맞습니다.
  • bindless 씬의 루트를 포함하는 버퍼를 레이 트레이싱 파이프라인에 바인딩합니다.
  • 이후에는 광선 추적 교차 검사를 수행하여 교차점을 찾은 다음 결과 개체에 대해 instance_id, geometry_id 및 primitive_id에 대한 정보를 쿼리 할 수 있습니다.

  • 이 예제에서는 intersect함수를 통해 교차점을 먼저 검출하여 전체에 대한 포인터를 얻습니다.
  • 이후에는 순차적으로 instance_id를 통해 인스턴스 버퍼를 찾고, geometry_id를 통해 광선이 히트한 메쉬를 결정할 수 있습니다.
  • 이후 각 메쉬 내에 광선이 적중한 primitive_id를 얻을 수 있습니다.

  • 위의 예제 코드를 보면 예제에서의 시나리오를 구현하였습니다.
  • 우선 교차 객체에서 instance_id를 검색하고 인스턴스 배열에서 인스턴스를 가져옵니다.
  • 인스턴스에서 geometry_id를 사용하여 메쉬를 가져옵니다.
  • 이후 메쉬에서 인덱스 버퍼 정보를 가져옵니다.

  • 삼각형이라고 가정하면 세 개의 인덱스를 차례로 가져옵니다.
  • 이 인덱스를 사용하여 정점 데이터 배열에 액세스하고 적절한 속성을 가져옵니다.

  • 이제 이 방법을 처음의 주전자 예제에 적용하면 적절한 그림자 색상을 지정해줄 수 있습니다.

  • 위의 예제는 광선 추적 반사를 적용한 하이브리드 렌더링 샘플입니다.
  • 바인딩 없이 화면을 인코딩하고, 광선 추적 셰이더에서 직접 해당 픽셀의 값을 올바르게 음영 처리한 결과입니다.

  • 광선이 트럭과 교차하는 지점에서의 반사 광선에 대한 셰이더의 출력을 직접 시각화할 수도 있습니다.
  • 이러한 방법으로 반사 알고리즘을 반복적으로 테스트할 수 있습니다.

  • 레스터화에서 픽셀을 적절하게 음영 처리하기 위해서도 동일한 원칙을 적용할 수 있으며 PBR 모델이 좋은 예가 됩니다.
  • PBR에서 Fragment 셰이더는 알베도, 거칠기, 금속성, 주변 폐색 등의 여러 텍스쳐를 적용해야 합니다.

  • 바인딩 모델에서는 각 슬롯에 개별적으로 텍스쳐를 바인딩해야 하지만, 바인딩을 사용하지 않는 모델은 인수 버퍼만 바인딩하면 되기 때문에 훨씬 간단합니다.
  • 또한 이 방법을 사용하면 draw 호출 수를 줄이고 인스턴스 렌더링을 사용하기 때문에 엔진을 더욱 최적화할 수 있습니다.
  • 이 방법을 사용할 때에서 모든 텍스쳐는 상주된 상태여야 합니다.

  • 바인드를 하는 일반적인 PBR 셰이더의 예는 위와 같습니다.
  • draw를 호출하기 전에 전달 인자로 참조되는 각 텍스처를 개별적으로 바인딩해야 합니다.
  • draw에 다른 텍스처 집합이 필요한 경우, 필요한 리소스들도 하나씩 바인딩해야 합니다.

  • 반면 바인드를 하지 않는 모델에서는 루트 인수 버퍼만을 전달합니다.
  • 해당 버퍼에서 인스턴스를 검색하고 그 인스턴스를 사용하여 적절한 Material를 가져와서 색상을 계산할 수 있습니다.

 

목차: https://nyancoder.tistory.com/2

Comments