shader에서 if 와 lerp의 성능은?
나의 궁금증은 아래와 같다.
1. shader 코드에서 동적분기로 return 시키는 것은 최적화에 도움이 되는가?
- 정적분기라면 굳. 확실히 최적화에 도움이 될듯. 컴파일에서 부터 정해지니까.
- 동적분기라면 비교값이 상수가 아닌 변수이므로 런타임에 정해진다.
- 마소 문서를 보면 if문에도 [branch]와 [flatten] 가 있으며 branch의 경우 if문의 부울이 먼저 평가 되므로
맞는 부분만 실행되고, flatten은 if의 양쪽 다 계산하고 맞는 부분을 결과값으로 선택하는 방식이다.
다만 branch는 tex2d 와 같은 그라디언트 함수를 쓰면 작동하지 않는다.
이는 주변 프래그먼트에 종속적이므로 실행되어야 하며, 각 프래그먼트를 건너뛰지 않아야 하기 때문이다.
2. if문 자체가 주는 부하를 감소시키기 위해 lerp, 삼항연산자 등을 쓰는게 도움이 되는가?
- 예를들어 아래와 같은 if문 형식이 있다면..
if(compare < 1)
{ 연산 a }
else
{ 연산 b}
이를 lerp(연산a, 연산b, step(compare, 1)) 로 바꾸거나 compare < 1 : (연산 a) ? (연산 b) 로 바꾸는게
더 좋은 방법인걸까?
-
3. if문이 정말 신경써야할 만한 부하를 야기하는가?
- https://answers.unity.com/questions/442688/shader-if-else-performance.html
- https://code.i-harness.com/ko-kr/q/2413290
- https://stonzeteam.github.io/Shader_Tutorial-6/
"The COMPLETE Effect And HLSL Guide" 내용 대략 번역..... 녹색글은 그냥 붙인 각주.
-------------------------------------------------------------------------------------------------
버텍스 셰이더 1.1 아키텍쳐에서는 동적 분기를 지원하지 않기 때문에 if문을 사용하면
if의 양쪽 셰이더 코드를 다 구현하도록 어셈블리 코드가 동작할 것이다.
코드는 선형적으로 실행되지만, if문의 한 부분의 결과만 결과로 사용된다.
vs1.1에서 컴파일 되는 예제코드를 살펴보자.
if(Value > 0)
Position = Value1;
else
Position = Value2;
위 식을 어셈으로 변형하면 아래와 같다.
// r0.w에 선형보간(lerp)값을 계산한다.
mov r1.w, c2.x // r1.w에 비교값 0을 넣는다.
slt r0.w, c3.x, r1.w // c3.x가 r1.w보다 작으면 r0.w는 1, 그렇지 않으면 0으로 설정
// 비교 결과값을 기반으로 value1과 value2 사이 값을 lerp한다.
mov r7, -c1 // -Value1의 값
add r2, r7, c0 // Value2 - Value1
mad oPos, r0.w, r2, c1 // r0.w * r2 + c1, 즉, 비교값*(Value2-Value1) + Value
결국 if문은 lerp의 계산과 값은 형식을 취한다는 것을 알 수 있다.
실제로 lerp의 계산이 x + s(y-x)로 되어있다.
위의 코드를 통해 if 구문의 두 표현식이 버텍스 셰이더 1.1모델에서 보간법으로 계산되어
올바른 결과를 결정하게 되는 것을 볼 수 있다.
실제 동적 흐름 제어에서, 이 표현식은 단지 두개의 명령문만 필요로 하지만, 이 경우에는 5개의 명령어가 필요하다.
게다가 if 구문은, 몇몇 하드웨어에서는 정적 또는 동적 반복을 허용하지만, 대부분은 선형 실행이 필요하다.
비록 HLSL이 1.x 픽셀셰이더를 제외한 모든 셰이더 모델에 대한 흐름제어 명령을 지원하지만,
진정한 흐름제어는 18개의 흐름제어 어셈블리 명령어 집합을 통해 3.0 버텍스 및 픽셀
셰이더가 있는 하드웨어에서만 지원된다.
이는 3.0이 아닌 셰이더모델이 흐름제어를 코드로 변환하여 조건문과 펼쳐진 루프의 양쪽을 다 실행한다는 것을 의미한다.
하드웨어 흐름제어가 그래픽스 하드웨어 내에서 아직 초기 단계이기 때문에, 그 성능은 여전히 한계가 있다.
적절한 실행을 위해 주의를 기울여야 한다.
7장에서 더 광범위하게 분기하는 것에 대한 성능 고려 사항을 논의 할 것이다.
대부분의 흐름 제어는 정적 또는 동적 타입이다.
정적 흐름제어에서, 명령문에 사용된 표현식은 실제로 불변(constant)하며 사전에 결정되어질 수 있다.
예를들면, 정적 분기는 boolean 셰이더 상수에 따라 코드 블록을 켜거나 끌 수 있다.
이 방식은, 현재 렌더링 되고 있는 오브젝트의 타입을 기반으로 코드 패스를 유효 또는 무효로
할 수 있는 편리한 방식이다.
렌더링 호출 사이에서 현재 셰이더에서 지원하길 원하는 기능을 결정 한 후에 그 동작을
얻기 위해 boolean 플래그를 설정할 수 있다.
반면에, 동적 분기는 대부분의 개발자들이 잘 알고 있고 있다.
동적 분기의 비교 조건은 변수에 있으므로, 런타임시에 비교가 수행된다.
성능 비용은 분기 비용 + 분기에서 가져오는 추가적인 명령어의 비용이다.
동적 분기는 동적 흐름 제어를 지원하는 특정 하드웨어의 버텍스 셰이더에서 사용할 수 있다.
-------------------------------------------------------------------------------------------------
DX8 : pixel shader 1.1
DX8.1 : pixel shader 1.3 & 1.4
DX9 : pixel shader 2
DX9.0c : pixel shader 3
DX10 : pixel shader 4
DX10.1 : pixel shader 4.1
DX11 : pixel shader 5
DX12 : pixel shader 5.x ( dx12가 오버헤드 절감에 초점을 맞춰 개발해서 새로운 shader model 버전은 미정)
Reference Link
- DirectX9 High Level Shading Language
'Study > Shader' 카테고리의 다른 글
shadertoy 시작하기. (0) | 2018.07.21 |
---|