TECHARTNOMAD | TECHARTFLOWIO.COM

UNITY3D

[번역글] Branching on a GPU

jplee 2023. 5. 16. 00:45

 

GPU 분기의 잘못 된 진실.

한동안 이것에 대해서 살펴보지 못하다가 훌륭한 유니티 플러그인 개발자인 Jason Booth 의 토픽을 읽고 공유 하기 위해 약식으로 번역 했습니다. 사실 처음 이것은 중국어로 번역했다가 다시 한국어로 번역 된 것이라고 해야 할까요? 중국 회사에서 팀원들에게 읽어보라고 권유 했던 토픽이었기 때문이죠.

 

원문은 링크를 타고 가시면 됩니다.

 

Branching on a GPU

If you consult the internet about writing a branch of a GPU, you might think they open the gates of hell and let demons in. They will say…

medium.com

[번역]

GPU에서 분기

GPU의 한 가지를 작성하는 것에 대해 인터넷을 참조한다면, 당신은 그들이 지옥의 문을 열고 악마를 들여 보내라고 생각할 수 있습니다. 그들은 당신이 어떤 희생을 치르더라도 그들을 피해야한다고 말할 것이고, 삼항 연산자 또는 step() 및 기타 어리석은 수학 트릭을 사용하여 그들을 피할 수 있다고 말할 것입니다. 이 조언의 대부분은 기껏해야 구식이거나 단순한 잘못입니다.

그것을 바로 잡자.

 

Branch 의 종류

GPU에서 분기를 작성할 때 고려해야 할 몇 가지 사항이 있습니다.

첫 번째는 어떤 종류의 데이터가 분기되고 있는지입니다. 예를 들어 해당 데이터가 상수 버퍼에서 가져온 경우 분기는 매우 저렴 할 수 있습니다. 이는 컴파일러가 분기가 모든 픽셀 또는 꼭짓점에 대해 동일한 방식으로 진행된다는 것을 알고 있기 때문에 일정한 버퍼 상태가 병렬로 처리되는 모든 픽셀 또는 정점에 대해 일정하게 보장되기 때문입니다.

그러나 해당 데이터가 동적 인 경우 텍스처의 값과 같이 병렬로 함께 처리되는 각 픽셀은 해당 분기를 처리 해야 합니다. 이 시점에서 컴파일러는 각 경로의 코드가 양쪽을 실행하고 올바른 결과를 선택할 만큼 충분히 작다고 결정하거나 적절한 분기가 필요하다고 결정할 수 있습니다. 그러나 픽셀이 이러한 방식으로 발산되면 해당 픽셀에 대한 다른 최적화가 중단 될 수 있습니다. 일반적으로 모든 픽셀을 변경하는 시끄러운 신호에서 분기를 피하려고 합니다.

두 번째 구성 요소는 각 분기에 어떤 종류의 코드가 있는지입니다. 브랜치 내부의 텍스처를 샘플링하려는 경우 그라디언트 또는 LOD 샘플러를 사용해야 합니다. 기본적으로 다음과 같이 코드를 작성해야합니다.

float2 dx = ddx(uv);
float2 dy = ddy(uv);UNITY_BRANCH // this is a Unity macro that forces a branch
if (_SomeConstant > 1)
{
   o.Albedo += SAMPLE_TEXTURE2D_GRAD(_Albedo, sampler_Albedo, uv, dx, dy);
}
else
{
   o.Albedo += SAMPLE_TEXTURE2D_GRAD(_Albedo2, sampler_Albedo2, uv, dx, dy);
}

매크로UNITY_BRANCH에 유의하십시오.이 명령은 GPU (지원하는 GPU)가 실제 분기를 수행하도록 강제하고 두 텍스처를 모두 샘플링하고 올바른 결과를 선택하도록 결정하지 않는 API 특정 명령으로 컴파일됩니다.

왜 우리는 이것을 필요로합니까? 2x2 쿼드 최적화를 유지하려면 GPU를 수행합니다. 이렇게하지 않으면 대부분의 플랫폼에서 컴파일 오류가 발생하고 다른 플랫폼에서는 양쪽이 실행되며 다른 플랫폼에서는 2x2 쿼드 최적화가 중단되고 여러 번 느리게 실행됩니다. 브랜치 전에 텍스처 샘플링에 사용되는 파생물을 가져와 전달함으로써 GPU가 잠재적으로 이러한 픽셀을 분산시키는 것을 방지합니다.

마지막으로 분기를 평가하는 방법을 고려해야합니다. Unity에서 아직 사용할 수 없는 최신 HLSL까지 다음 코드를 고려하십시오.

if (_Constant > 1 && SomeFunc() > 1)
{
}

CPU와 달리 _Constant이 0이면 SomeFunc() 가 여전히 호출되고 평가됩니다.

 

분기 작업을 위한 프레임워크

자산 저장소의 모든 셰이더에서 다음과 같은 코드를 찾을 수 있습니다.

#if _BRANCHSAMPLES
   #if _DEBUG_BRANCHCOUNT_TOTAL
     float _branchWeightCount;
     #define MSBRANCH(w) if (w > 0) _branchWeightCount++; if (w > 0)
   #else
     #define MSBRANCH(w) UNITY_BRANCH if (w > 0)
   #endif
#else
   #if _DEBUG_BRANCHCOUNT_TOTAL
     float _branchWeightCount;
     #define MSBRANCH(w) if (w > 0) _branchWeightCount++;
   #else
     #define MSBRANCH(w) 
   #endif
#endif

이 코드를 사용하면 지정된 픽셀에서 실행되는 분기 수를 쉽게 계산할 수 있으며 브랜치가 사용되는 경우 쉽게 전환 할 수 있으며 픽셀 당 얼마나 많은 브랜치가 사용되는지 표시 할 수있는보기 모드를 쉽게 전환 할 수 있습니다. 이것의 주요 용도는 잠재적으로 발산 경로에서 취해지는 분기의 빈도를 시각화 할 수 있다는 것입니다. 나는 종종 특정 기능 (삼평면, 확률 등)을 위해 이것들을 만들 것입니다.

매크로의 또 다른 집합을 사용하면 각 픽셀에서 실제 텍스처 샘플을 계산할 수 있으므로 취해지는 샘플을 쉽게 시각화 할 수 있습니다.

#if _DEBUG_SAMPLECOUNT
   int _sampleCount;
   #define COUNTSAMPLE { _sampleCount++; }
#else
   #define COUNTSAMPLE
#endif

이것들을 사용하는 것은 다음과 같이 보일 것입니다 :

half4 a0 = half4(0,0,0,0);
half4 a1 = half4(0,0,0,0);
half4 a2 = half4(0,0,0,0);
MSBRANCH(tc.pN0.x)
{
   a0 = MICROSPLAT_SAMPLE_DIFFUSE(tc.uv0[0], config.cluster0, d0);
   COUNTSAMPLE
}
MSBRANCH(tc.pN0.y)
{
   a1 = MICROSPLAT_SAMPLE_DIFFUSE(tc.uv0[1], config.cluster0, d1);
   COUNTSAMPLE
}
MSBRANCH(tc.pN0.z)
{
   a2 = MICROSPLAT_SAMPLE_DIFFUSE(tc.uv0[2], config.cluster0, d2);
   COUNTSAMPLE
}

이 데이터를 화면에 출력하려면 다음을 수행 할 수 있습니다.

#if _DEBUG_BRANCHCOUNT
   o.Albedo = (float)_branchWeightCount / 12.0f; 
#endif

MicroSplat에서 삼평면 브랜치를 시각화합니다. 밝은 영역은 더 많은 가지에 대해 사실로 테스트되었습니다.

샘플을 시각화하기 위해 사용자가 설정할 수있는 임계 값 속성을 추가합니다 - 샘플 카운트는 임계 값 위에 노란색으로 그려지고 아래의 것은 빨간색 음영으로 그려집니다.

#if _DEBUG_SAMPLECOUNT
   float sdisp = (float)_sampleCount / max(_SampleCountDiv, 1);
   half3 sdcolor = float3(sdisp, sdisp > 1 ? 1 : 0, 0);
   o.Albedo = sdcolor;
#endif

MicroSplat에서 동일한 카운트를 시각화합니다. 빨간색 영역은 9 샘플 미만이며 노란색 영역은 9 샘플 이상입니다. 분기를 사용하지 않도록 설정하면 셰이더는 픽셀당 28개의 샘플을 사용하므로 이 간단한 경우 분기를 사용하여 샘플링 비용의 2/3을 절약할 수 있습니다.

또 다른 예로, 이번에는 Triplanar, Stochastic Texture Clusters를 사용하여 지형 중량 분기를 사용하도록 설정합니다. 먼저 시스템은 텍스처 가중치에 따라 컬링되므로 특정 텍스처가 해당 픽셀에 사용되지 않으면 삼평면 및 확률 검사도 컬링됩니다. 그런 다음 삼평면, 확률 :

최종 렌더링

분기 시각화. 브랜치의 빈도는 훨씬 높고 더 다양한 픽셀을 생성하지만 영역은 여전히 전반적인 승리가 될만큼 충분히 큽니다.

샘플 카운트 시각화. 분기가 없으면 셰이더는 픽셀당 100개의 샘플을 수행합니다. 분기가 활성화된 상태에서 빨간색 영역은 픽셀당 34개 샘플 미만이고 노란색 영역은 34개 이상입니다. 수집 된 샘플의 범위는 픽셀 당 9 ~ 72 샘플입니다.

이러한 기술을 사용하면 GPU가 수행하는 작업을 정확하게 시각화하고 복잡한 셰이더에서 상당한 대역폭을 줄일 수 있습니다. 또한 다양한 기능에서 분기를 활성화 및 비활성화하거나 데이터를 독립적으로 볼 수 있으므로 최적화가 실제로 최적화되었는지 확인할 수 있습니다.

 

요약

  • GPU에서 분기하는 것을 두려워하지 마십시오, 많은 경우 완전히 괜찮습니다.
  • 당신이 무엇을 분기하고 있는지, 그리고 당신이 무엇을 분기하고 있는지 아십시오.
  • 고주파 데이터에서 분기하고 다른 픽셀을 만들지 마십시오.
  • 텍스처 주위를 분기하려면 특별한주의가 필요합니다.
  • GPU가 수행하는 작업을 정확하게 볼 수 있도록 데이터를 시각화합니다.
  • 가능한 경우 가장 가능성이 높은 지점에서 가장 가능성이 낮은 지점으로 지점을 주문하십시오.

[번역의 끝]

 

다음에는 이런 저런 최적화에 대해서 고민 했던 부분이나 알고 있는 부분들에 대해서도 직접 토픽을 작성 해 봐야겠습니다.