Dark Secrets of Shader Development

반응형

지인분께 추천받은 문서기도 하고 제목이 재미있어서 한번 번역해봄.

내맘대로 번역이니 원문보는걸 강추!! 개인적인 내용도 좀 추가.


제목 : 

Dark Secrets of Shader Development or What Your Mother Never Told You About Shaders.

셰이더 개발의 흑막 또는 당신의 어머니가 셰이더에 대해  당신에게 절대 말해주지 않은 것.


개요

● 셰이더란 무었인가?

- 셰이더 컴파일 과정

● 셰이더 최적화

- 특정 하드웨어에 국한되지 않는 셰이더 최적화

- ATI 하드웨어에 국한되는 셰이더 최적화 

   버텍스 셰이더 최적화

   픽셀 셰이더 최적화



셰이더란 무었인가?

● 셰이더는 그래픽 하드웨어에서 버텍스와 픽셀엔진을 컨트롤하는 마이크로 프로그램.

● DirectX에서 셰이더는 API를 통해 하드웨어에 보내어지는 토큰의 스트림

- 토큰들은 특별 코딩된 어셈블리 명령어들이다.

- 드라이버는 원래형태의 shader들을 받는다.

- Directx 셰이더들은 특정 의사코드(p-code)

- 드라이버는 다른 명령어와 마찬가지로 매크로를 받는다.



드라이버는 셰이더로 무슨짓을 하는가?

● Directx 셰이더들은 하드웨어 셰이더 구현과 완벽하게 일치하지 않는다.

● 드라이버는 특정 하드웨어 플랫폼을 특별한 하드웨어 마이크로 코드로 컴파일 한다. (u-code)

● 드라이버는 특정 하드웨어 플랫폼에 맞게 셰이더를 최적화 한다.

- 신줄하게 디자인된(잘 만든) 셰이더는 드라이버가 특정 하드웨어에서 셰이더 코드를 

   효율적으로 최적화 할 수 있게 한다.

셰이더 컴파일 프로세스 (번역 프로그램)




다양한 셰이더 컴파일 단계

HLSL code: float x = (a*abs(f)) > (b*abs(f));

Assembly code : 

  abs r7.w, c2.x

  mul r2.w, r7.w, c0.x

  mul r9.w, r7.w, c1.x

  slt r0.w, r9.w, r2.w

DirectX p-code

  02000023 80080007 a0000002

  03000005 80080002  80ff0007 a0000000

  03000005 80080009  80ff0007 a0000001

  0300000c d00f0000   80ff0009 80ff0002

Hardware specific u-code 

  983a28f41595

  329d8d123c04

  329d8d627c08

  3b487794333a



셰이더 최적화

● 최적화 - 가능한한 실용적으로 또는 효과적으로 어떤것을 완벽하게 만드는 과정.

- 개발 프로세스를 최대한 효율적으로 만든다.

- 셰이더를 가능한한 빠르게 수행하도록 만든다.

- 렌더링된 이미지가 가능한한 완벽하게 보이도록 만든다.

● 최적화는 성능, 이미지 품질 및 작업 효율을 균형을 이루도록 하는 기술이다.

● 개발과 비쥬얼 디버깅을 위해 렌더몽키같은 툴을 사용한다.

● HLSL을 사용하고 알고리즘 최적화에 집중한다.

● 셰이더 프로세스의 특정 저수준 최적화를 사용한다. (즉, 벡터형식 계산같은.)

- 하드웨어와 어셈블리에 대한 지식이 필요하다.



최적화 1단계 (HLSL) 고유 함수 사용

● 이미 있는것을 다시 만들려고 시간을 낭비하지말고, 고유 함수을 사용하자.

- 코드는 사용가능한 모든 하드웨어 기능을 사용한다.

- 각 셰이더 모델에 맞게 코드를 최적화 해라. 



최적화 1단계 (HLSL) 고유 함수 사용

● HLSL 코드의 어셈블리 번역...




최적화 2단계 (HLSL) 적절한 데이터 타입 사용

● 연산에 가장 적절한 데이터 타입을 사용해라 (float, float2, float3 그리고 float4)

- 스칼라 연산 대신에 벡터를 사용하지 마라

- float3 사용할수 있는곳에 float4를 사용하지 마라.

 이 최적화는 가능한한 HLSL 컴파일러 그리고/또는 드라이버 최적화기가 셰이더 명령을 쌍으로 하는것을 허용한다.

(pair shader instruction ?? 무슨뜻이지)



최적화 3단계 (HLSL) 타입캐스팅 줄이기

● 필요없는 타입캐스팅은 하지마라 - 사용되지 않는 벡터 구성요소를 간접적으로 초기화 한다(즉, 알파 채널)

 그리고 위 코드가 어떻게 어셈블리로 변환되는 지 보면...



최적화 4단계 (HLSL) 정수형계산을 피해라

● 연산에 정수대신에 float을 사용하자.

 HLSL은 정수 연산을 지원하지만, 대부분의 하드웨어는 지원하지 않는다.

 컴파일러는 int 타입 지원을 에뮬레이트 한다

- 정밀도와 범위는 다를수 있다.

- 약간의 추가코드가 만들어 진다.

 어셈블리 코드로 int의 비효율을 확인할 수 있다.



최적화 5단계 (HLSL) 인덱싱을 위해 정수형을 사용하라

● 상수배열을 인덱싱할때 float보다는 int를 사용하자

 어셈블리 코드를 보면 float이 반올림연산을 더해주는 것을 볼 수 있다.



최적화 6단계 (HLSL, ASM) 스칼라 상수를 묶어라

● 벡터에 스칼라 상수들을 넣어서 합쳐라.

- 상수의 갯수가 줄어든다.

- 하드웨어의 제한 사항을 해결할 수 있다.( 포트 읽기 제한 )

 위 최적화에 대한 어셈코드는 아래와 같다.



최적화 7단계 (HLSL) 상수 배열을 묶어라

● 상수 벡터로 상수 배열을 묶어라.

- 이전 최적화 팁과 비슷한 내용이다.



최적화 8단계 (HLSL) 상수선언을 올바르게 하자

● 조건부 컴파일의 경우 static으로 선언된 boolean 상수를 사용한다.

 어셈블리 코드를 보면 static으로 선언하지 않았을 때 두 분기가 모두 평가된다



최적화 9단계 (HLSL, ASM) 벡터화 계산

● 가능하다면 언제나 비슷한 연산을 결합하여 코드를 벡터화 하자.

- 한번에 최대 4-x 작업을 수행할 수 있다.

● 어셈블리 코드에서 최적화 모습



최적화 10단계 (HLSL, ASM) 좀 더 벡터화 하기

● 비슷한 방법을 사용해서 셰이더에서 사용 할 수 있는 특수 명령어를 활용해라. (dot 명력어 같은걸 사용해라)

- 예 : a+b+c+d <=> a*1 + b*1 + c*1 + d*1 <=> dp4

● 어셈블리에서 최적화 모습



최적화 11단계 (HLSL) 벡터화 비교

● 현재 HLSL의 조건 연산자는 스칼라를 벡터로 올바르게 승격시켜 주지 않는다.

- 즉 a = (c > 0.5f) ? 0.1f : 0.9f;

● 벡터화 비교 어셈블리



최적화 12단계 (HLSL) 전치행렬에 유의해라

● HLSL코드에서 행렬을 전치하는 것을 피해라

● 전치행렬에 의한 곱셈을 위한 mul()순서를 반대로 사용해라

● 가능한한 열 정렬 행렬을 사용해라.

● 열 정렬 행렬 변환은 4개의 MUL/MAD 대신에 3개의 DP4 명령어를 사용한다



최적화 13단계 (PS:HLSL,ASM) 스위즐을 현명하게 사용해라

● PS 2.0은 임의의 스위즐을 지원하지 않는다.

- 기존의 스위즐을 창의적으로 사용해라. (즉, WZYX는 .ZW를 역순으로 접근할 수 있다.)

- 스위즐은 상수 팩킹과 벡터화에 사용할 수 있다.

● 어셈블리에서 최적화..

- .WZ 대신 .ZW를 사용하면 한개가 아니라 두개의 MOV 명령어가 생성된다.

왼쪽은 (io+a)*c를 위해 add와 mul 후 r0.xy에 저장하였고 (i1+b)*d를 위해 add와 mul후 r1.xy에 저장하였다.

오른쪽은 t를 위해 (i01+ab)*cd인 add와 mul 결과를 r0에 저장하고 r0.wz값을 r1.xy에 복사하였다.



최적화 14단계 (PS:HLSL) 1D 텍스쳐 Fetch를 사용해라

● DirectX 1D 텍스쳐는 Nx1 차원의 2D 텍스쳐로 에뮬레이트 된다.

● 1D 텍스쳐를 Fetch할려면 비록 실제로는 2D라고 해도 tex1D라는 특수 명령 함수를 사용해야 한다.

● 어셈블리 코드..



최적화 15단계 (PS:HLSL, ASM) Signed 텍스쳐를 사용해라.

● signed data를 표현하기 위해 signed 텍스쳐 포맷을 사용해라. (노멀맵 같은)

● PS 2.0은 _bx2 수정자를 지원하지 않으니 unsigned data의 범위를 확장하기 위해 추가 MAD 명령이 필요하다.

_bx2 : 각 채널에서 0.5를 뺸 결과에 2.0 곱하기, 0~1 범위를 -1~1로 변형시 사용. ps1.1~1.4 지원

mad : 곱하고 더함



(뒤쪽 내용은 ati 한정에 옛날 라데온 모델이라 안읽으려 했지만....심심하니 읽어보는걸로..)

ATI 하드웨어 특정 최적화

● 라데온 계열의 DirectX9 최적화 

● 버텍스 셰이더 최적화

● 픽셀 셰이더 최적화

● 픽셀 셰이더에서의 정밀도



라데온 9500+를 위한 버텍스 셰이더 최적화

● 몇가지 최적화 규칙만 적용된다.

- 드라이버가 당신을 위해 모든 궃은 일을 한다.

● ATI DirectX9 하드웨어에 가장 관련있는 버텍스 셰이더 최적화

- 셰이더의 정점 데이터 출력

- 명령어 공동 발행

- 흐름 제어 사용



ATI 버텍스 셰이더 최적화 1단계 버텍스 데이터 출력

● 가능한 한 빨리 계산된 정점 위치를 export 해라.

- 드라이버는 그 정보로 당신을 잘 도와 줄 것이다.

- 버텍스 위치를 계산하는 가장 짧은 명령 행은 최적화기가 작업을 수행할 수 있게 한다.

(그냥 짧게 짜란 말인듯)

 셰이더에서 필요한 정보만 출력

- 동일한 데이터로 texture coordinate들을 출력하지 않는다. (출력 TEXCOORD를 아껴써라 인듯)

- 텍스쳐 좌표를 샘플러에 매핑하기 위해 PS1.4와 PS2.0을 사용한다.



ATI 버텍스 셰이더 최적화 2 단계 명령어 공동 발행

● 버텍스 프로세서 아키텍처는 버텍스 셰이더 명령들(클럭당 4D+1)을 픽셀 셰이더와 다소 비슷하게 공동 발급할 수 있다.

 다음 규칙을 따르면 공동 발행 가능성이 높아진다.

- 출력 레지스터에 쓰는데 스칼라 연산을 사용하지 않는다.

- POW,EXP, LOG, RCP 그리고 RSQ 명령어에서 쓰기 mask (.w)를 사용해라.

- 읽기포트 제한은 명령 쌍에 적용된다는 것을 기억하라.

(우선 라데온 9500이 버텍스 셰이더처리에 두개의 수학 엔진을 가지고 있고 하나는 벡터를, 하나는 스칼라를 연산하며

같은 클럭에 명령어를 처리해 준다는걸 알고나서 봐야 이해가 좀 되는데, 이에 대한 자세한 설명은

Performance Optimization Techniques for ATI Graphics Hardware with DirectX® 9.0 을 참조하면 된다.)



ATI 버텍스 셰이더 최적화 3 단계 흐름제어

● VS 2.0은 상수기반 정적분기와 루프를 지원한다.

- 셰이더 관리를 단순화 하고 셰이더 수를 줄인다.

- 흐름 제어 명령은 무료

- 드라이버가 흐름 제어 실행을 최적화 한다.

● 셰이더 최적화의 범위가 제한되어 있으므로 흐름 제어 기능이 있는 셰이더의 퍼포먼스는 저하될 수 있다.



라데온 9500+를 위한 픽셀 셰이더 최적화

● 최적화된 픽셀 셰이더는 그래픽 퍼포먼스를 상당히 향상시킨다.

- 드라이버는 신중하게 설계되어진 셰이더를 최대한 활용할 수 있다.

 ATI 그래픽스 하드웨어의 픽셀 셰이더 최적화 에서 가장 중요한 것들

- 텍스쳐 명령어들

- 텍스쳐 읽기에 의존하는 것들 (종속적 텍스쳐 읽기)

- ALU 명령어들

- 밸런싱 명령어

- 명령어 공동 발행

- PS1.4 사용



ATI 픽셀 셰이더 최적화 1단계 TEXKILL 명령어

● 가능한 한 TEXKILL 명령어 사용을 피해라.

- 버텍스 레벨에서 클리핑을 할수 있다면 user clip plane에서 TEXKILL을 사용하지 마라.

● ATI 하드웨어에서 TEXKILL 명령어에 대한 진실

- 셰이더에서 TEXKILL의 위치가 성능에 영향을 끼치지는 않는다.

- 셰이더는 선처리능력이 없다.

TEXKILL 명령어는 "top of the pipe z-reject" 효율에 영향을 준다. (early-z와 같은말인듯)

TEXKILL은 텍스쳐 명령어이며, 셰이더의 의존선 레벨을 만드는데 기여한다.



ATI 픽셀 셰이더 최적화 1단계 의존성 레벨과 TEXKILL

TEXKILL을 사용해서 픽셀 셰이더에 추가 종속수준을 만드는 예.

- 텍스쳐 읽기가 없는 첫번째 ALU 명령어는 이 예제에서 의존성 레벨로 간주된다.

(이 페이지는 정확하게는 이해가 안가는데 대략적으로 TEXKILL을 사용해서 아래쪽을

discard 여부가 있으니까 아래쪽 코드는 texkill에 종속적이며 discard 되면 아래쪽은 안타니까

최적화에 사용할수 있다? 이런건가...)



ATI 픽셀 셰이더 최적화 2단계 깊이값 계산

● 픽셀 셰이더에서 깊이값을 출력하는 경우의 제한.

- PS 1.4 - TEXDEPTH 명령어 (이 픽셀에 대한 깊이 버퍼의 비교 테스트로 사용하는 깊이값을 계산)

- PS 2.0 - oDepth 레지스터로 출력 (깊이 테스트용 레지스터)

 픽셀 셰이더에서 깊이값을 출력하는 것에 대한 진실.

- Hyper-Z 효율성에 영향

- "top of the pipe Z-reject"에 간섭.



ATI 픽셀 셰이더 최적화 3단계 종속적 텍스쳐 읽기

● 종속적 텍스쳐 읽기가 무료가 아니라는 것을 명심해라

- 1-2 레벨은 높은 성능으로 실행된다.

- 3-4 레벨은 느리기실행되지만, 실제 사용에는 여전히 성능적으로 적당하다

 종속성 수준에 따라 동등하게 명령어를 분산시켜라.

 종속성 레벨을 추가하는것을 피해라

 불필요한 종속적 텍스쳐 읽기 예


(연산의 결과나 다른 텍스쳐의 값을 텍스쳐 좌표로 사용하는것을 하지 말라는 말인듯)

픽셀 셰이더는 마이크로코드 집합으로 그래픽 프로세서로 다운로드 되어서 

그래픽 프로세서 상에서 수행되며 픽셀과 텍셀들에 대해 작용한다.

버전 1.4는 명령집합을 통합하고 단계라는 개념을 도입했다.

첫번째 단계는 텍스쳐 샘플링과 텍스쳐 주소 연산에 관련된다.

두번째 단계는 색연산과 함께 종속적 텍스쳐 읽기가 허용된다.

자세한 내용은 "dependent texture read(종속적 텍스쳐 읽기) , 원문 "을 참조.



ATI 픽셀 셰이더 최적화 4단계 산술 명령어

● 모든 단순한 명령들은 각 파이프에서 1 명령/클럭 비율로 실행된다.

● 대부분의 매크로는 DirectX9 설명서에 적혀있는대로 실행된다.

- 매크로 SINCOS 는 8 클럭.

● 계산된 결과를 바로 재사용 할 경우 패널티가 없다.

- 존재하지 않는 대기시간을 숨기기 위해 재정렬하는 "똑똑함"을 발휘하지 마라.



ATI 픽셀 셰이더 최적화 5단계 명령 밸런싱

● 픽셀 엔진은 동시에 텍스쳐를 읽고 ALU 작업을 수행할 수 있다.

● 텍스쳐 대역폭이 병목이 아니라면 텍스쳐 명령어의 수를 산술 명령어 수에 가깝게 유지해라.

● 룩업 텍스쳐를 사용하여 ALU 명령어와 밸런싱을 맞출 수 있다.

● 그러나 늘 이미지 품질을 신경써라.



ATI 픽셀 셰이더 최적화 6단계 명령 공동 발행

● PS 2.0 모델은 명령 쌍을 지원하지 않는다.

● RADEON 9500+는 여전히 "듀얼 파이프" 디자인을 기반으로 한다.

● RADEON 9500+의 PS2.0에서는 벡터계산(RGB 파이프)과 스칼라 연산(알파 파이프)을

같은 사이클로 실행하는 것이 가능하다. 

● 드라이버가 공동 발행을 위해 최적화 한다.

● 자동 명령 쌍 이점을 얻기 위한 규칙

- 컬러 파이프와 알파 파이프 간의 연산 부하를 분산한다.

- PS 2.0은 명시적인 pairing이 없다. 드라이버에서 자동 공동발행을 위해 쓰기 마스크(.rba 와 .a)를 사용해라.

(Real-time Shader Programming책 참고.)

- 알파 파이프에서만 스칼라 명령 RCP, RSQ, EXP 그리고 LOG을 사용해라

-코드에서 서로 가깝게 배치해서 최적화기가 공동발행된 명령어를 찾기 쉽도록 만들어라.

● 예 : 디퓨즈와 스펙큘러 라이팅 연산



ATI 픽셀 셰이더 최적화 7단계 PS 1.4 사용

● PS 2.0 모델은 많은 명령어와 피연산자 지정 수정자를 노출하지 않는다.

● RADEON 9500+ 아키텍쳐는 여전히 많은 오래된 수정자를 지원한다.

● 많은 수정자를 필요로 하는 저렴한 셰이더에서 수정자를 접근하기 위해 PS 1.4을 사용해라.



픽셀 셰이더의 정밀도

● RADEON 9500+에서는 모든 픽셀 연산이 24 bit float 포멧에서 일어난다.

● ATI 하드웨어는 의도적으로 부분정밀 모드를 지원하지 않는다.

- 텍스쳐 좌표로 작업할 때 정밀도가 충분하지 않다.

- 반사, 스펙큘러 라이팅 그리고 절차적 텍스쳐 연산을 할 때 품질이 낮다.

 최적화는 시네마틱 품질이 목표일 때 특히 이미지 품질을 고려해야 한다.



픽셀 셰이더에서의 정밀도 예제

● 점 광원 소스

● 픽셀 셰이더에서 노멀 벡터 정규화 : cubemap vs NRM 명령어


역시 그냥 슥 읽어볼 때는 시간이 그렇게 많이 걸리지 않는데 정리하는데는 시간이 많이 걸린다..

오래된 자료다 보니 요즘과 상황이 안맞는 내용도 좀 있으니 참고.



Reference Link

- pdf

- 마이크로 프로그램

- Direct3D Driver Shader Codes

- How to compile a shader

- 컴파일러의 구조

- 바이트 코드(p-code)

- dp3 관련 설명

- HLSL Language Basics (열우선 행렬 관련 설명)

- msdn, Source Register Swizzling

- signed, unsigned texture 관련

- msdn dp3

- msdn, _bx2

- Performance Optimization Techniques for ATI Graphics Hardware with DirectX® 9.0

- msdn, texkill(픽셀 셰이더)

- GDC10 : Rendering Wounds in Left 4 Dead 2

- nvidia, custom clip plane

- z buffer optimization

- ATI GPU Architecture

- msdn, texdepth

- msdn, oDepth

- 위키, HyperZ

- dependent texture read(종속적 텍스쳐 읽기) , 원문 

- msdn sincos

- msdn nrm

-

TAGS.

Comments