Graphics Programming

[번역] RTX Best Practices 본문

Season 2

[번역] RTX Best Practices

minseoklee 2022. 8. 26. 20:23

언젠가 직접 RTX 코딩할 일 있으면 참고할 것들

티스토리 편집기 왜 이렇게 무거워졌지...

 

https://developer.nvidia.com/blog/best-practices-for-using-nvidia-rtx-ray-tracing-updated/

 

 

글은 게임에 NVIDIA RTX 레이트레이싱을 활용하며 얻어온 우리의 경험에 근거한 모범 사례들을 수집한 것입니다. 현재 레이트레이싱을 작업하고 있는 개발자들을 위한 실용적인 팁들을 간략하고 바로 실천할 있는 형태로 정리했습니다. 팁들의 목적은 어떤 솔루션들이 대부분의 경우 좋은 퍼포먼스를 내는 지에 대한 통찰을 주는 것입니다. 특수 사례에 대한 최적 솔루션을 찾는다면 항상 프로파일링하고 실험해보는 것을 권장합니다.

 

본문에서 흔하게 쓰이는 축약어들:

  • AABB: Axis-aligned bounding box
  • AS: Acceleration structure
  • BLAS: Bottom-level acceleration structure
  • Geometry: A geometry in a BLAS
  • Instance: An instance of a BLAS in a TLAS
  • TLAS: Top-level acceleration structure

1 Acceleration structures

절에서는 어떤 용도로든 레이트레이싱을 사용하려 시작점이 되는, 레이트레이싱 가속 구조(acceleration structure) 빌드와 관리에 초점을 둡니다. 다음 화제들을 다룹니다.

  • 일반적인
  • 빌드 GPU 활용의 극대화
  • 메모리 할당
  • 지오메트리들을 BLAS 조직화하기
  • preference 플래그 지정하기
  • 다이나믹 BLAS
  • 반투명/투명 지오메트리
  • 파티클

1.1 일반적인

AS 빌드할 async compute 고려하세요. 특히 하이브리드 렌더링에서 G버퍼나 섀도우맵을 래스터화하는 도중 AS 빌드를 async compute 실행하면 이득이 있습니다.

 

워커 쓰레드를 이용해 AS 빌드 커맨드 리스트를 생성하는 것을 고려하세요. AS 빌드 커맨드를 생성하는 것은 CPU 측에서 상당량의 작업이 있습니다. AS 빌드 호출 자체 때문일 수도 있고, 물체 컬링 같은 연관 작업 때문일 수도 있습니다. CPU 작업을 하나 이상의 워커 쓰레드들로 옮기면 이득이 있습니다.

 

TLAS 위한 인스턴스들을 컬링하세요. TLAS 전체를 포함하는 것은 일반적으로 최적이 아닙니다. 대신 상황에 따라 인스턴스들을 컬링하세요. 예를 들어 확장된 카메라 프러스텀에 기반하여 컬링하는 것을 고려해보세요. 최대 거리는 래스터화의 far plane 거리보다 짧을 있습니다. 컬링할 인스턴스 크기를 고려하여 작은 인스턴스들은 짧은 거리에서 컬링할 수도 있습니다.

 

인스턴스들에 적절한 LOD 사용하세요. 래스터화의 경우와 비슷하게 모든 것에 가장 디테일한 지오메트리 LOD 사용하는 것은 대개 최적이 아닙니다. 멀리 있는 물체들에는 간단한 LOD 사용할 있습니다. 하이브리드 렌더링에서는 래스터화와 레이트레이싱에 같은 LOD 사용할 수도 있습니다. 표면이 스스로에 그림자를 드리우는 self-intersection 결함 같은 것을 피하는 데도 효과적입니다.

 

그리고 레이트레이싱에 낮은 디테일의 LOD 사용하여, 특히 다이나믹 BLAS 업데이트 비용을 줄이는 것을 고려하세요. 래스터화와 레이트레이싱 간의 LOD 일치하지 않으면 self-intersection 방지하기 위해 레이트레이싱 back face 컬링을 해야 수도 있습니다. 레이트레이싱에서의 LOD stochastic LOD 구현하는 방법에 관해 많은 정보는 Implementing Stochastic Levels of Detail with Microsoft DirectX Raytracing(https://developer.nvidia.com/blog/implementing-stochastic-lod-with-microsoft-dxr/) 읽어보세요.

 

가능하면 항상 지오메트리나 인스턴스에 불투명 플래그를 지정하세요. 인스턴스나 지오메트리에 불투명 플래그를 붙이면 하드웨어 교차 검사를 방해하지 않고 any-hit 셰이더의 호출을 방지합니다. 가능하면 항상 이것을 지켜주세요. any-hit 셰이더는 반드시 필요한, 예를 들면 알파 테스팅을 위한 지오메트리 등에만 활성화하세요.

 

가능하면 항상 삼각형 지오메트리를 사용하세요. 하드웨어는 ray-삼각형 교차 검사를 수행하는데 뛰어납니다. ray-박스 교차 검사도 가속되기는 하지만 삼각형 지오메트리를 트레이싱하면 하드웨어를 최대한 활용할 있습니다.

1.2 빌드 GPU 활용도(utilization) 극대화

버텍스 변형(deformation) BLAS 빌드를 하나로 묶으세요. BLAS 빌드의 입력으로서 사용될 삼각형들을 생산하는 버텍스 변형 호출과 모든 BLAS 빌드 호출을 연이어서 실행하세요. 호출들 사이에 리소스 배리어를 배치하지 마세요. 그러면 드라이버가 호출들을 최대한 병렬화합니다. 모든 BLAS 빌드 호출에는 배리어 없는 실행을 위해 고유의 스크래치 메모리가 필요합니다.

 

BLAS 들고 있는 리소스를 위한 개별 UAV 배리어들은 필요하지 않습니다. 대신 TLAS 빌드에 앞서 모든 BLAS 빌드가 완료되었음을 보장하기 위한, 하나의 전역 UAV 배리어만 있으면 됩니다. 리소스가 어디 있는지는 상관 없습니다.

 

작은 버텍스 변형 호출들을 병합하는 것을 고려하세요. 어떤 지오메트리나 인스턴스를 위해 변형된 버텍스들을 생산하는 호출들이 경량이여서 연이은 호출 사이에 배리어 없이 실행해도 GPU 최대한 활용하지 않는 경우가 있습니다. 여러 지오메트리나 인스턴스의 처리를 하나의 호출로 병합하면 GPU 활용도를 증가시켜 퍼포먼스가 향상됩니다.

 

1.3 메모리 할당

작은 할당들을 풀링(pooling)하세요. BLAS들은 킬로바이트에 달할 정도로 작을 있습니다. 그런 작은 BLAS들을 별개의 committed resource 보관하는 것은 최적이 아닙니다. 대신 이것들을 리소스에 풀링하세요. 풀링은 메모리를 절약하고 가끔은 퍼포먼스를 향상시킵니다. 가능한 선택지로서 리소스 안의 placed resource들을 이용하는 방법이 있습니다.

 

또는 단일 버퍼 안에서 직접 부분할당(suballocate)하여 여러 BLAS 해당 버퍼에 저장할 수도 있습니다. 부분할당은 256 바이트 정렬만 지키면 되기 때문에 BLAS들을 메모리에 타이트하게 집어넣을 있습니다. 어떤 풀링 메커니즘을 사용하던 풀링에 의해 얻는 이득을 유지하기 위해 메모리 파편화를 피하세요. 많은 정보는 Managing Memory for Acceleration Structures in DirectX Raytracing(https://developer.nvidia.com/blog/managing-memory-for-acceleration-structures-in-dxr/) 보세요.

 

정적 BLAS들을 압축(compact)하는 것을 고려하세요. BLAS들을 압축하면 메모리를 절약하고 퍼포먼스를 향상시킬 있습니다. 메모리 소비를 얼마나 줄일 있는 지는 지오메트리에 따라 다르지만 최대 50% 달할 있습니다. 압축된 크기는 GPU에서 BLAS 빌드가 끝난 CPU 다시 읽어와야 하므로, 지침은 번만 빌드되는 BLAS들에 가장 실용적입니다. 압축으로부터 최대한 이득을 얻기 위해 작은 할당들을 풀링하고 메모리 파편화를 피해야 함을 기억하세요. 많은 정보는 Tips: Acceleration Structure Compaction(https://developer.nvidia.com/blog/tips-acceleration-structure-compaction/) 보세요.

 

1.4 지오메트리들을 BLAS 조직화하기

인스턴스의 월드 스페이스 AABB 공간이 많으면 BLAS 쪼개는 것을 고려하세요. 월드 스페이스 AABB ray 어떤 인스턴스와 교차할 가능성이 있는지 테스트하고 필요하다면 연관된 BLAS 순회하는데 쓰입니다. 공간이 매우 크면 BLAS 불필요하게 순회하게 됩니다.

 

각자 독립적으로 움직이는 지오메트리들은 일반적으로 고유의 BLAS 가져야 합니다. 이것들을 단일 BLAS 묶으면 공간이 많은 AABB 되고, 독립적인 인스턴스들의 트랜스폼만 단순히 바꾸는 대신 불필요하게 BLAS 리빌드하게 됩니다.

 

 

Figure 1. AABB 겹치고 공간이 많은 BLAS 안의 지오메트리들 (왼쪽). 지오메트리들을 독립된 BLAS 쪼갠 AABB들이 더이상 겹치지 않음 (오른쪽).

 

인스턴스 월드 스페이스 AABB들이 심하게 겹치면 BLAS들을 병합하는 것을 고려하세요. 인스턴스들의 월드 스페이스 AABB들이 겹치면 구역을 거치는 개별 ray 가능한 교차점을 찾기 위해서 겹치는 BLAS 인스턴스들을 따로 처리해야 합니다. 하나의 병합된 BLAS 순회하는 것이 효율적입니다.

 

어떤 BLAS 순회하는 퍼포먼스는 안의 지오메트리 개수에 영향을 받지 않습니다. 단일 BLAS 병합된 지오메트리들은 여전히 고유의 머티리얼을 가질 있습니다.

 

Figure 2. AABB 겹치는 BLAS 인스턴스들 (왼쪽) 하나로 병합된 BLAS 인스턴스 (오른쪽).

 

가능하면 항상 BLAS들을 인스턴스화하세요. BLAS 인스턴싱하면 메모리를 절약합니다. 레이트레이싱 퍼포먼스도 향상시킬 있습니다. 인스턴스들은 고유의 머티리얼과 트랜스폼을 가질 있습니다. 인스턴스들의 AABB 많이 겹치는 경우, 여러 지오메트리의 단일 BLAS 복제 병합하는 것이 메모리 소비를 증가시킴에도 여전히 나은 방법일 있습니다.

 

지오메트리 안에 길쭉한 삼각형이 있는 것을 지양하세요. 길고 가는 삼각형들의 바운딩 볼륨은 공간이 많아서 최적이 아닙니다. 그런 삼각형들은 다른 바운딩 볼륨들과 많이 겹치게 됩니다. 지오메트리에 대해 ray 추적할 최적이 아닌 퍼포먼스를 내게 됩니다.

 

지오메트리에 따라 드라이버가 이런 문제를 어느 정도 해결해주기도 합니다. 그런 삼각형이 하나 있는 것은 문제가 되지 않겠지만 너무 많으면 문제가 되므로, 가능하면 그런 삼각형을 작은 삼각형들로 쪼개는 식으로 회피하는 것을 추천합니다.

 

스카이 지오메트리를 TLAS 포함하지 마세요. 스카이박스나 skysphere 다른 모든 것과 겹치는 AABB 가지며, 모든 ray 이를 검사해야 것입니다. 스카이 셰이딩을 히트 셰이더에서 스카이를 나타내는 지오메트리를 통해 처리하는 것보다는 미스 셰이더에서 처리하는 것이 효율적입니다.

 

1.5 빌드 선호(preference) 플래그

TLAS에는 PREFER_FAST_TRACE 플래그를 고려하고 항상 리빌드만 하세요. 이러면 대부분 전반적으로 최고의 퍼포먼스를 냅니다. 장면 내에서 발생하는 움직임이 얼마나 중요한가와 무관하게 TLAS 최대한 하이 퀄리티로 빌드하세요. 빌드 비용이 그렇게 비싸지도 않습니다.

 

스태틱 BLAS에는 PREFER_FAST_TRACE 플래그를 사용하세요. 번만 빌드하는 모든 BLAS 대해서는 최고의 레이트레이싱 퍼포먼스를 위해 최적화하는 것이 손쉬운 선택입니다.

 

다이나믹 BLAS에는 PREFER_FAST_TRACE 또는 PREFER_FAST_BUILD 플래그 하나를 선택하거나, 혹은 쓰지 마세요. 자주 리빌드되거나 업데이트되는 BLAS들은 상황에 따라 최적의 빌드 선호 플래그가 달라집니다. 얼마나 많이 빌드되는가? 레이트레이싱이 얼마나 비싼가? async compute에서 빌드를 실행하여 빌드 비용을 감출 있는가? 특수 사례에 대한 최적의 해법을 찾는다면 여러 옵션을 시도해보세요.

 

1.6 다이나믹 BLAS

가능하면 기존 BLAS 재활용하세요. 이전 업데이트 이후 BLAS 정점들이 움직이지 않은 것을 있다면 기존 BLAS 계속 사용하세요.

 

눈에 보이는 물체들만 BLAS 업데이트하세요. TLAS로부터 컬링된 인스턴스들이 있으면 BLAS 업데이트 프로세스에서 그것들의 컬링된 BLAS 제외하세요.

 

거리와 크기에 기반해 업데이트를 건너 뛰는 것을 고려하세요. BLAS 화면 상에서 얼마나 지에 따라 프레임 갱신할 필요가 없을 있습니다. 눈에 띄지 않는 시각적 오차를 감수하고 일부 업데이트를 건너 수도 있습니다.

 

변형 후에는 BLAS 리빌드하세요. 일부만 변형된 BLAS 업데이트하는 것은 리빌드보다 훨씬 싸기 때문에 좋은 선택입니다. 하지만 이전 리빌드 변형이 일어나면 최적의 레이트레이싱 퍼포먼스를 없습니다. 길쭉한 삼각형들은 문제를 크게 만듭니다.

 

업데이트되는 BLAS들을 주기적으로 리빌드하는 것을 고려하세요. 지오메트리가 너무 많이 변형되어서 최적의 레이트레이싱 퍼포먼스를 복구하려면 리빌드가 필요한 시점을 감지하는 것은 쉽지 않습니다. 잠재적인 심각한 퍼포먼스 문제를 피하기 위해 변형도와 무관하게 모든 BLAS 주기적으로 리빌드하는 것이 합리적인 접근법일 있습니다.

 

리빌드를 여러 프레임에 걸쳐 분산하세요. 리빌드는 업데이트보다 상당히 느리기 때문에 프레임에 리빌드를 많이 하면 버벅일 있습니다. 이를 피하기 위해 리빌드를 여러 프레임에 분산하는 것이 좋습니다.

 

예측할 없는 변형에 대해 리빌드만 하는 것을 고려하세요. 지오메트리 변형이 크고 잦을 경우 BLAS 빌드 ALLOW_UPDATE 플래그를 생략하고 항상 리빌드만 하는 것이 이득일 있습니다. 필요하다면 리빌드 비용을 줄이기 위해 PREFER_FAST_BUILD 플래그를 수도 있습니다. 극단적인 경우 PREFER_FAST_BUILD 플래그를 쓰면 PREFER_FAST_TRACE 플래그 업데이트를 쓰는 것보다 전반적인 레이트레이싱 퍼포먼스가 좋아질 수도 있습니다.

 

BLAS 업데이트에서 삼각형 토폴로지 변화를 피하세요. 업데이트 도중 토폴로지 변화라 함은 삼각형이 degenerate 또는 revive하는 것을 뜻합니다. 이러면 degenerate 삼각형의 꼭지점들이 revive 삼각형의 꼭지점들을 나타내지 않을 경우 레이트레이싱 퍼포먼스가 최적이 아닙니다. "구부리는" 변형에서 토폴로지 변화가 잦은 것은 대체로 문제가 되지 않지만 "쪼개지는" 변형에서 토폴로지가 크게 변화하는 것은 문제가 있습니다.

 

가능하다면 "쪼개지는" 변형에 의해 토폴로지가 달라지는 삼각형들을 위해 별도의 BLAS 버전들을 마련하거나 inactive 삼각형들을 이용하세요. 삼각형의 꼭지점이 NaN이면 inactive하다고 합니다. 이런 대안들을 없다면 토폴로지 변화 BLAS 업데이트하는 대신 리빌드하는 것을 추천합니다. 인덱스 버퍼 수정을 통한 토폴로지 변화는 업데이트 도중에는 허용되지 않습니다.

 

1.7 반투명 지오메트리

가능하면 반투명 영역을 최소화하세요. 대개 반투명 삼각형의 알파 테스팅을 위해 쓰이는 any-hit 셰이더는 하드웨어 교차 검사를 방해합니다. 가능하면 opaque 마크되지 않은 영역을 최소화하는 것이 퍼포먼스를 늘리는 쉬운 방법입니다. 반투명 영역을 정밀하게 정의하기 위해 삼각형을 많이 쓰는 것도 좋은 트레이드 오프입니다.

 

불투명 지오메트리와 반투명 지오메트리로 쪼개는 것을 고려하세요. 지오메트리 삼각형들의 일부분이 확실히 완전 불투명하면 그것들을 별도 지오메트리로 쪼개고 opaque 마크하는 것을 고려할 있습니다. 서로 다른 지오메트리들은 여전히 동일 BLAS 내에 포함될 있습니다.

 

1.8 파티클

빌보드 파티클을 삼각형 지오메트리로 표현하는 것을 고려하세요. BLAS 안에서 빌보드 파티클을 표현하는 방법 하나는 빌보드를 삼각형으로서 출력하고 빌보드 일부를 수직 축을 따라 다른 방향으로 90 돌리는 것입니다. 이러면 삼각형 교차 검사 하드웨어를 활용하면서 파티클의 시각적 경계를 그럴 듯하게 근사할 있습니다.

 

블렌딩 대신 알파 테스팅을 고려하세요. 파티클 유형에 따라 primary visibility 렌더링할 블렌딩되는 파티클을 위한 secondary ray에서는 알파 테스팅을 사용해도 시각적 품질이 괜찮을 있습니다. 접근법은 경계가 명확한 파티클에 가장적합합니다. 연기나 안개 같은 파티클에는 적용할 없습니다. 많은 정보는 Ray Traced Reflections in 'Wolfenstein: Youngblood(https://www.gdcvault.com/play/1026723/Ray-Traced-Reflections-in-Wolfenstein)' 보세요.

 

죽은 파티클에 degenerate 삼각형을 사용하지 마세요. 업데이트되는 BLAS 들어있는 degenerate 삼각형은 구조체를 레이트레이싱에 최적이 아니게 만듭니다. 활성 파티클 개수가 변하는 파티클 시스템의 경우 프레임 올바른 파티클 개수를 가지고 BLAS 리빌드하는 등의 다른 해결책을 고려하는 것을 추천합니다.

 

메시 파티클을 TLAS 안의 인스턴스로 표현하는 것을 고려하세요. 삼각형 메시로서 렌더링되는 파티클들의 경우, 파티클마다 고유한 인스턴스를 가지는 것이 적절한 해결책이 있습니다. 특히 파티클들이 장면 여기저기 흩어져서 개별 ray 많은 인스턴스에 충돌하지 않을 경우 그렇습니다. 인스턴스들은 기반 메시 BLAS 공유해야 합니다. 그리고 BLAS 압축(compact)하는 것도 고려하세요.

 

2 히트 셰이딩

절은 레이 히트 시의 셰이딩에 초점을 맞춥니다. 숙련된 그래픽스 개발자들도 레이트레이싱 셰이더 개발을 이제 시작하는 참이라면 다음 아이디어들에서 무언가를 얻을 있을 것입니다. 최적의 솔루션이 래스터라이제이션에서와 다를 수도 있기 때문입니다.

 

  • 일반적인
  • 발산의 최소화
  • Any-hit 셰이더
  • 셰이더 리소스 바인딩
  • 인라인 레이트레이싱
  • 파이프라인 스테이트

 

2.1 일반적인

ray 페이로드를 적게 유지하세요. 페이로드 값들을 보관하는 데는 레지스터들이 사용되고, 페이로드 값들은 히트 셰이더에서 사용할 있는 레지스터 개수를 감소시킵니다. 부주의한 페이로드 활용은 지양하는 것을 추천하지만, 값들을 패킹하는 복잡한 코드는 대부분 득이 되지 않습니다.

 

페이로드 액세스 한정자(qualifier) 활용하세요. 피처는 HLSL 셰이더 모델 6.6에서 활용 가능합니다. 한정자는 어떤 셰이더 스테이지에서 페이로드의 필드를 쓰거나 읽는 지를 명시할 있으며, 컴파일러가 레지스터 활용을 최적화할 있도록 하며, 이는 높은 점유율(occupancy) 나은 퍼포먼스로 이어집니다. 최대한 이득을 보려면 필드의 한정자를 가능한 정확하게 정의하세요. 많은 정보는 깃헙의 DirectX-Specs(https://microsoft.github.io/DirectX-Specs/d3d/Raytracing.html#payload-access-qualifiers) 보세요.

 

사용하지 않는 페이로드 필드에 안전한 기본값을 쓰는 것을 고려하세요. 어떤 셰이더가 페이로드의 모든 필드를 활용하지 않지만 다른 셰이더들에 필드들이 필요하다면, 쓰지 않는 필드에 안전한 기본값을 기록하는 것이 좋을 있습니다. 이러면 컴파일러가 쓰이지 않는 입력값을 폐기하고, 페이로드 레지스터가 실제로 쓰이기 전까지 다른 목적으로 활용할 있습니다.

 

첫번째 히트에서 ray들을 최대한 종료하세요. (shadow ray처럼) 정확한 closest hit 필요가 없는 경우 ray RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH 또는 gl_RayFlagsTerminateOnFirstHitEXT 플래그를 지정하면 간단하면서 효율적으로 최적화할 있습니다.

 

페이스 컬링은 정확성이 필요할 때만 사용하세요. 래스터라이제이션과 달리 back face 또는 front face 컬링은 퍼포먼스를 향상시키지 않습니다. 오히려 ray 순회를 조금 느리게 만듭니다. 정확한 렌더링 결과를 얻기 위해 필요할 때만 사용하세요.

 

레이트레이스 호출에 걸친 활성 상태를 최소화하세요. 어떤 변수들이 TraceRay 또는 traceRayExt 호출에 앞서 초기화되고 호출 이후에도 사용된다면, 변수들은 히트 셰이더 또는 미스 셰이더를 실행하는 동안 유지되어야 하는 활성 상태들입니다. 드라이버에는 이에 대처하는 수단이 가지 있지만 모두 비용이 들어갑니다.

 

활성 상태의 양을 최소화하는 것을 추천드립니다. 그런 변수들을 식별하는 것이 항상 자명하지는 않습니다. 엔비디아와 마이크로소프트는 그런 활성 상태를 자동 탐지하는 컴파일러 피처를 위해 협력하고 있습니다.

 

깊은 재귀를 피하세요. 깊고 균일하지 않은 ray 재귀는 비용이 비쌀 있습니다.

 

2.2 발산(divergence) 최소화

머티리얼 모델마다 별개의 히트 셰이더를 사용하세요. 히트 셰이더 안에서 코드와 데이터의 발산을 줄이면 특히 비응집적(incoherent) ray들의 경우 도움이 됩니다. 특히 머티리얼 모델마다 직접 스위칭하는 우버셰이더를 피하세요. 필요한 머티리얼 모델마다 별개의 히트 셰이더를 구현하면 시스템이 발산하는 히트 셰이딩을 최적으로 관리할 있습니다.

 

사용하는 머티리얼 모델이 크게 발산하지 않는 통합 셰이더를 지원한다면, 머티리얼이 여러 개인 지오메트리들에 공통된 히트 셰이더를 사용하는 것도 고려해볼만 합니다.

 

단순화된 셰이딩을 고려하세요. primary visibility 렌더링을 위한 모든 피처를 specular reflection이나 indirect diffuse illumination 그대로 가져올 필요는 대개 없습니다. 가지 피처를 덜어내도 항상 시각적으로 크게 차이가 나지는 않습니다. 달리 말하면 시각적 개선이 렌더링 비용을 정당화하지는 않습니다. ray들이 비응집적일 수록 primary visibility 피처를 정확히 그대로 복제할 필요가 없어집니다. (역주: indirect diffuse 같은 상황) 또한 히트 거리가 길어질 수록 셰이딩을 간소화할 있습니다.

 

버텍스 셰이더와 픽셀 셰이더를 그대로 컨버팅해오지 마세요. 히트 셰이딩에서 최적의 퍼포먼스를 내는 접근법은 래스터라이제이션에서 최적인 것과 다른 경우가 있습니다. 래스터라이제이션에서는 코드가 조금 달라도 별개의 셰이더 퍼뮤테이션을 만드는 것이 이득일 있습니다. 히트 셰이딩에서는 개별 히트 셰이더에서의 발산을 줄이는 아니라 그런 히트 셰이더의 개수를 줄이는 것도 이득이 됩니다. 일반적으로 저는 버텍스 픽셀 셰이더를 그대로 히트 셰이더로 컨버팅하는 것을 추천하지 않습니다.

 

공용 코드를 히트 미스 셰이더의 밖으로 빼는 것을 고려하세요. 모든 히트 셰이더가 공통 분모를 가지면 코드를 히트 셰이더 , 예를 들면 raygen 셰이더로 빼내세요. hit-and-miss 셰이더들에서도 공용 코드가 있는 경우가 있습니다. 가령 히트 셰이더에서 다음 바운스를 위한 근사가 미스 셰이더의 첫번째 바운스에서 처리되는 근사와 같은 경우가 있습니다. 이런 때도 공용 코드를 hit-and-miss 셰이더들 밖으로 빼는 것을 추천합니다.

 

2.3 Any-hit 셰이더

통합되고 간소화된 any-hit 셰이더를 우선시하세요. any-hit 셰이더는 ray 순회 도중 많이 호출될 있고 하드웨어 교차 검사를 방해할 있습니다. any-hit 셰이더의 비용은 전반적인 퍼포먼스에 크게 영향을 끼칩니다. 레이트레이싱 패스에서는 통합되고 간소화된 any-hit 셰이더를 추천드립니다. 또한 any-hit 셰이더에서는 GPU 전체 레지스터 capacity 활용할 없는데, 드라이버가 ray 상태를 보관하는데 레지스터 일부를 사용하기 때문입니다.

 

머티리얼 데이터 액세스를 최적화하세요. any-hit 셰이더에서 머티리얼 데이터 액세스를 최적화하는 것이 핵심인 경우가 있습니다. 의존 관계가 있는 일련의 메모리 액세스는 흔한 패턴입니다. 버텍스 인덱스, 버텍스 데이터를 로드하고 텍스처를 샘플링하는 것처럼요. 이러한 패스에서 indirection 가능한 줄이면 이득이 됩니다.

 

블렌딩 히트 순서가 정의되지 않음을 주의하세요. ray 방향을 따라 발견된 충돌들과 그에 해당하는 any-hit 셰이더 호출들의 순서는 정의되지 않습니다. 따라서 블렌딩 테크닉들은 order-independent해야 합니다. 최근접 불투명 히트 너머의 히트들을 제외하려면 ray 거리를 적절하게 제한해야 한다는 뜻이기도 합니다. 이에 더해 블렌딩되는 지오메트리들에 올바른 결과를 위해 NO_DUPLICATE_ANYHIT_INVOCATION 플래그를 지정해야 수도 있습니다. 많은 정보는 Ray Tracing Gems(http://www.realtimerendering.com/raytracinggems/rtg/index.html) 9장을 보세요.

 

2.4 셰이더 리소스 바인딩

가급적 글로벌 루트 테이블(DXR) 또는 다이렉트 디스크립터 액세스(Vulkan) 선호하세요. 종종 raygen 셰이더와 miss 셰이더가 사용하는 리소스들은 셰이더 레코드를 통하는 대신 컴퓨트 셰이더에서 하듯이 바인딩하는 것이 편한 경우가 있습니다. 또한 어떤 것에 히트했는지와 무관하게 사용되는 hit 셰이더 리소스들도 비슷하게 바인딩할 있습니다. 모든 히트 레코드에 동일 리소스가 바인딩되게 하는 것은 최적이 아닙니다.

 

hit 셰이더에 bindless 리소스를 고려하세요. unbounded 디스크립터 테이블(DXR) 또는 unsized 디스크립터 배열(Vulkan) 안의 리소스들을 hit-specific 시스템 값들(InstanceIndex, gl_InstanceID, 또는 hit record(DXR root constant) 직접 저장되는 ) 통해 인덱싱하면 hit shader 리소스들을 효율적으로 공급할 있습니다.

 

인덱스 버퍼와 버텍스 버퍼에 루트 디스크립터를 고려하세요. (DXR) unbounded 디스크립터 테이블의 대안으로서 인덱스 버퍼와 버텍스 버퍼의 주소를 hit record 안에 root descriptor로서 저장하면 효율적입니다. 루트 디스크립터를 통해 리소스에 액세스할 때는 암묵적인 아웃 오브 바운드 검사를 하지 않습니다. 루트 디스크립터 주소는 4바이트 정렬되어야 합니다. 베이스 주소에 대한 16비트 인덱스들의 오프셋을 미리 계산하면 정렬을 깨트릴 있습니다.

 

가능하면 루트 시그너처 버전 1.1 스태틱 디스크립터를 활용하세요. (DXR) 루트 시그너처 1.1 디스크립터가 스태틱임을 드라이버가 예측할 있도록 합니다. 그런 디스크립터는 커맨드 리스트가 레코딩되고 나면 애플리케이션 측에서 수정되지 않습니다. 경우 드라이버 측에서 잠재적으로 최적화할 여지가 있고, 특히 루트 디스크립터가 버퍼 액세스에 사용되지 않을 경우에 그러합니다. 루트 디스크립터의 경우처럼 스태틱 디스크립터에는 암묵적인 아웃 오브 바운드 검사를 하지 않습니다. 또한 스태틱 디스크립터와 루트 디스크립터는 null 아니여야 합니다.

 

셰이더 테이블을 GPU에서 빌드하는 것을 고려하세요. 지오메트리와 레이트레이싱 패스가 많을 경우 히트 테이블이 많이 커져서 업로딩에 상당한 시간이 걸리게 있습니다. 히트 테이블 전체를 CPU에서 빌드한 업로드하는 대신, 현재 보이는 인스턴스들을 위한 머티리얼 인스턴스 같은, 프레임 새로운 정보만 필요한 대로 업로드하세요. 다음 히트 테이블 빌드 패스를 GPU에서 실행하면 효율적입니다.

 

히트 그룹 식별자, 버텍스 버퍼 주소, 지오메트리들의 오프셋 테이블 빌드에 필요한 많은 정보는 GPU 메모리에 영속적으로 남을 있습니다.

 

2.5 인라인 레이트레이싱

8x8 이상의 쓰레드 그룹 사이즈를 고려하세요. 인라인 레이트레이싱을 수행하는 컴퓨트 셰이더에는 통상적으로 8x8 쓰레드 그룹 사이즈를 사용할 있습니다. 일반적으로 그룹 쓰레드 개수가 GPU 웨이브 사이즈의 배수인 것이 효율적입니다. NVidia GPU들의 웨이브 사이즈는 32 쓰레드입니다.

 

하지만 웨이브 제한 하나만 고려하여 쓰레드 그룹을 활용하면 동시에 실행할 있는 그룹 개수의 제한으로 인해 쓰레드 점유율(occupancy) 제한됩니다. 그룹 안에 웨이브를 포함하면 잠재적인 점유율을 배로 만들 있습니다. 셰이더 레지스터와 그룹 셰어드 메모리 소비도 점유율을 제한할 있습니다. 다른 영향 요인들에 문제가 없는 경우, 그룹에 웨이브 3 이상을 포함하면 최대 쓰레드 점유율을 달성할 있습니다.

 

따라서 실용적인 그룹 사이즈로 16x8 쓰레드를 선택할 있습니다. 이상 사이즈를 늘리는 것은 대개 이득이 되지 않습니다. 여러 사이즈를 실험해보면 특수 케이스에 대한 최적의 사이즈를 찾을 있습니다. 최적의 사이즈는 하드웨어 세대에 따라 다를 있습니다.

 

인라인 레이트레이싱에서 발산 셰이딩을 피하세요. 히트 셰이더들이 히트에 기반해 호출되지 않기 때문에 모든 셰이딩은 ray 캐스팅하는 셰이더 안에서 인라인으로 계산됩니다. 셰이더 안에 히트에 기반해 발산하는 코드 패스나 데이터 액세스가 있으면 셰이딩을 느리게 만들고, 특히 비응집적 ray들의 경우 더욱 그러합니다. 서로 다른 셰이딩 모델들을 처리해야 하면 DispatchRays 또는 vkCmdTraceRaysKHR 나은 선택지입니다.

 

인라인 레이트레이싱에서는 bindless 리소스 액세스를 위해 hit-specific 시스템 값들을 사용하세요. 히트 레코드 내의 바인딩들을 활용할 없기 때문에 지오메트리에 특수한 바인딩들은 다른 수단으로 제공해야 합니다. unbounded 디스크립터 테이블 내의 리소스들을 InstanceContributionToHitGroupIndex GroupIndex 같은 hit-specific 시스템 값을 통해 접근하는 것이 좋은 방법입니다.

 

인덱스, 버텍스, 머티리얼 데이터에 접근할 때는 indirection 최대한 피하는 것을 추천합니다. 예를 들어 InstanceID 같은 시스템 값에 기반해 어떤 버퍼로부터 리소스 인덱스를 읽고, 이를 이용해 인덱스 버퍼를 선택하는 것은 감추기 어려운 레이턴시를 만들 있습니다.

 

컴파일 타임 ray 플래그를 선호하세요. 인라인 레이트레이싱에서는 컴파일 타임 런타임 ray 플래그들을 모두 사용할 있습니다. 가능하면 컴파일 타임 플래그를 추천드립니다. 잠재적인 컴파일 타임 최적화로부터 이득을 얻을 있기 때문입니다.

 

쿼리 오브젝트들의 레지스터 소비를 모니터링하세요. 초기화 , 셰이더가 순회를 계속할 수도 있는 코드를 실행하고 있을 쿼리 오브젝트들은 상태를 계속 유지하고 있어야 합니다. 여기에는 레지스터가 소비되므로 복잡한 유저 코드에 의해 점유율이 일반적인 상황보다 빠르게 제한될 있습니다. 이는 DispatchRays 또는 vkCmdTraceRaysKHR 패스에서 any-hit 셰이더를 실행하는 것과 유사한 상황입니다. 어떤 변수가 쿼리 오브젝트를 사용하기 전에 초기화되고 쿼리 이후 사용된다면, 그런 변수는 레지스터를 추가로 소비할 수도 있습니다.

 

응집성 향상을 위해 쓰레드 그룹 재배열을 고려하세요. 컴퓨트 셰이더에서 인라인 레이트레이싱을 디스패치되는 쓰레드 그룹들은 기본적으로 row-major 정렬이 되는데, 이러면 최적의 퍼포먼스를 내지 못할 수도 있습니다. 쓰레드 그룹들이 동시 수행하는 메모리 액세스들의 응집성은 쓰레드 그룹들을 직접 재배열하여 개선할 있습니다. 많은 정보는 Parallel Shader Compilation for Ray Tracing Pipeline States(https://developer.nvidia.com/blog/parallel-shader-compilation-ray-tracing-pipeline-states/) 보세요.

 

2.6 파이프라인 스테이트

raygen 셰이더마다 스테이트 오브젝트를 따로 사용해보세요. DisplayRays 또는 vkCmdTraceRaysKHR 호출마다 패스에 필요한 셰이더들만 포함하는 스테이트 오브젝트를 따로 준비하는 것을 추천합니다. 레지스터 소비를 최적화 뒤에서 설명할 파이프라인 config 값들의 최적 설정에 도움이 됩니다.

 

MaxTraceRecursionDepth, MaxRecursionDepth, MaxPayloadSizeInBytes, MaxAttributeSizeInBytes 가능한 작게 만드세요. 이것들을 필요 이상으로 높게 잡으면 퍼포먼스에 부정적인 영향을 줍니다. DispatchRays vkCmdTraceRaysKHR 호출에서 인라인 레이트레이싱을 사용할 이런 레이트레이스 호출들은 최대 재귀 깊이를 따지는데 포함되지 않습니다.

 

가능하면 최대한 SKIP_PROCEDURAL_PRIMITIVES, SKIP_AABBS, SKIP_TRIANGLES 사용하세요. 파이프라인 스테이트 플래그들은 스테이트 컴파일 간단하지만 효과적인 최적화가 있습니다.

 

병렬 컴파일과 공유를 위해 셰이더 컬렉션을 고려해보세요. (DXR) 많은 셰이더를 관리할 셰이더 컬렉션은 스테이트 오브젝트들의 멀티쓰레드 컴파일과 스테이트 오브젝트간 컴파일된 코드 공유를 가능하게 만들기도 합니다. 많은 정보는 Parallel Shader Compilation for Ray Tracing Pipeline States(https://developer.nvidia.com/blog/parallel-shader-compilation-ray-tracing-pipeline-states/) 보세요.

 

자동 바인드 포인트 할당이 필요하면 컴파일러 옵션을 고려해보세요. (DXR) 기본적으로 셰이더 리소스들의 자동 바인드 포인트 할당은 셰이더 라이브러리 컴파일 시에는 사용되지 않습니다. 그런 것이 필요하다면 유용한 컴파일러 옵션이 있습니다. 먼저 /auto-binding-space 주어진 레지스터 스페이스에 자동 바인드 포인트 할당을 활성화합니다. 또한 static 키워드를 붙이지 않은 모든 함수는 기본적으로 라이브러리 익스포트로 간주됩니다.

 

/auto-binding-space 사용하면 임의의 익스포트된 함수에서 액세스하는 리소스들은 모두 실제로 최종 스테이트 오브젝트에서 사용되는지와 무관하게 바인드 포인트를 소비합니다. 바인드 포인트 소비를 정말 필요한 함수들로 제한하려면 /exports 사용해 라이브러리 익스포트를 제한할 있습니다.

 

점진적 바인딩을 위해 AddToStateObject 고려하세요. AddToStateObject 쓰면 기존 오브젝트들에 기반해 스테이트 오브젝트들을 점진적으로 빌드할 있습니다. 이는 셰이더가 많은 다이나믹 컨텐트를 관리할 유용할 있습니다.

 

가능하면 스택을 직접 관리하세요. API 쿼리 함수를 통해 셰이더마다 필요한 스택 크기를 결정하고 호출 그래프에 관한 측의 지식을 활용해 메모리 소비를 줄이고 퍼포먼스를 향상시키세요.

 

2 shadow ray 쏘는 값비싼 반사 셰이더가 좋은 예시입니다. 앱은 셰이더가 스택을 많이 쓰지 않는 자명한 히트 셰이더만 사용하는 것을 알고 있습니다. 드라이버는 이런 호출 그래프를 미리 없으므로 보수적으로 잡히는 기본 스택은 메모리를 과하게 할당하게 됩니다.

 

3

히트맵을 구현하는 것을 고려하세요. 특정 BLAS 특정 지오메트리의 셰이딩 관련 퍼포먼스 이슈를 파악하기 위해 엔비디아는 히트맵과 픽셀의 처리 비용 시각화를 구현하는 편의 API 제공합니다. 이는 레이트레이싱 패스들의 퍼포먼스를 개선하는데 유용할 있습니다. 많은 정보는 Profiling DXR Shaders with Timer Instrumentation(https://developer.nvidia.com/blog/profiling-dxr-shaders-with-timer-instrumentation/) 보세요.

 

NVIDIA Nsight Graphics 사용해 프로파일링하고 디버깅하세요. Acceleration structure, 셰이더 테이블을 들여다보고 레이트레이싱 패스를 프로파일링하세요. (https://developer.nvidia.com/nsight-graphics)

 

Nsight Graphics 효율적으로 사용하는 방법이 궁금하면 다음 글들을 읽어보세요.

 

마이크로소프트 셰이더 컴파일러를 최신 버전으로 업데이트해보세요. (DXR) 새로운 피처와 최적화를 위해 Microsoft Shader Compiler(https://github.com/microsoft/DirectXShaderCompiler) 최신 버전으로 업데이트하는 것을 고려해보세요.

Comments