Render Hell - Book 2 (+알쓸유잡 강의 참고) 정리

2023. 9. 5. 23:26배움엔 끝이없다/그래픽스

쉐이더 스터디 내용 공유를 위해 작성했습니다.

참고 강의 영상: 알쓸유잡 - 야 너두 쉐이더 개발할 수 있어!

주 참고 자료: Render Hell - Book 2

 

1. GPU의 내부 시스템들 (Nvidia GPU 기준)

GPU 코어는 per vertex / pixel 연산을 할 수 있지만,

tessellation이나 culling 등 복잡한 Fixed pipeline의 작업들은 전문 하드웨어 블록이 따로 존재한다.

 

Streaming Multiprocessor(SM)는 하나의 쉐이더에 속한 stream of verticies/pixels를 담당하고, 이에 대한 작업을 여러 GPU 코어에 분배한다.

하나의 GPU에 여러개의 SM이 있기 때문에, 2개 이상의 쉐이더를 동시에 실행할 수 있다.

 

2. GPU의 pipeline

아래 과정 중 4~8의 과정은 Shader를 실행할 때 일어나는 일이다.

 

1. Application(게임)에서 Graphics Driver에게 API를 통해 원하는 명령을 전달한다.

 

2. Driver는 받은 명령을 토대로 Command Buffer를 채운다.

 - 이 과정에서 오버헤드가 발생하는데, 각 API Call마다 오버헤드가 있어 API 호출을 최소화하는게 좋다. 최신 API에서는 이 오버헤드가 줄긴 했지만, 그래도 여러 API call을 큰 하나의 call로 만드는 것이 정석이다.

 

3. GPU의 Host Interface가 Command Buffer를 읽는다.

 

4. GPU가 RAM에서 VRAM으로 읽어와야할 데이터(Vertex buffer, Shader parameter 등)가 있을 수 있고, 이는 GPU 내부 담당 엔진이 읽어온다. 

 

5. Gigathread Engine이 vertex/pixel마다 쓰레드를 만들어 Thread Block으로 묶는다. 그리고 이 것들은 SM들에게 분배된다.

 

6. SM 내부의 Polymorph Engine은 VRAM에 있는 정점데이터 등을 캐쉬로 복사해온다.

 

7. SM이 Thread Block을 32(up to 64)개의 Warp로 쪼갠다.

 

8. 각 Wrap을 GPU 코어가 실행한다.

 - GPU 코어는 Branching을 할 수 없어서, 만약 if문이 쉐이더 내에 있으면 모든 브랜치를 다 실행하게 된다. (코어의 절반은 브랜치 A, 절반은 브랜치 B를 실행하는 방식인데, 브랜치 A를 실행하는 동안 나머지 절반의 코어는 쉰다.)

 - 따라서 이는 코어를 n배로 소모해 비효율적이라, 분기가 있는 쉐이더는 컴파일 타임에 다른 쉐이더로 나눠주는게 좋다.

 - Warp를 하나의 SM이 여러개 들고 있는 이유는, 어떤 Warp는 특정 데이터가 계산되기까지 기다려야하는데, 이 Memory Stall 동안 다른 Warp를 실행하기 위해서다.

 

3. Rendering Pipeline

1. Vertex Shader에서 per vertex operation을 해준다. 주로 Transformation이나 Lighting 등을 담당한다.

 

2. Tessellation Stage는 더 세밀한 표현을 위해 폴리곤을 늘리는 작업으로, optional하다.

2-1. Patch Assembly 단계에선 표면을 쪼개기 위해 정점 단위로 관리되던 데이터를 표면으로 변환한다.

2-2. Hull Shader는 포면을 쪼개기 위한 parameter인 tesselation factor (inner, outer)를 계산해 반환한다.

2-3. Tesselation 단계에선 tesselation factor에 맞게 표면에 여러 vertex를 추가해 면을 여러개로 만든다.

2-4. Domain Shader는 Hull Shader의 output(Patch Assembly의 output)과 Tesselator의 output을 받아서 쪼개진 표면을 이루는 정점들의 위치를 결정한다.

 - Displacement Map이 이 단계에서 사용된다.

 

3. Primitive Assembly 단계에선 정점 단위로 관리되던 데이터를 해석해 primitive(점, 선, 면)를 만든다.

 

4. Geometry Shader는 Rasterizing 직전에 마지막으로 primitive를 변환하는 쉐이더로, 정점을 추가하거나 변환할 수 있다. 추가적으로 점을 삼가형으로 변환하는 등 primitive를 다른 primitive로 바꿀 수도 있다.

 

5. Rasterizing 전에 지금까지 변환된 점들 (Projection Space좌표)을 Viewport에 맞게 변환하고(=Viewport transformation), Viewport의 경계에서 잘린 primitive를 자른다(=Clipping).

 

6. 하나의 GPU엔 여러 Rasterizer가 있고 각 Rasterizer가 담당하는 화면 내 구역이 있는데, primitive가 걸친 Rasterizer에게 해당 primitive 정보를 전달한다.

 

7. Rasterizer가 primitive를 픽셀에 대응시킨다. 각 픽셀에는 primitive의 attributes가 interpolate돼 전달된다. 이 후 depth testing을 통해 그려야하는 픽셀을 정한다. (다른 primitive에 가려진 부분은 pixel shader로 전달되지 않는다.)

 

8. Pixel Shader 혹은 Fragment Shader는 interpolate된 attributes를 이용해 각 pixel의 색을 정한다.

 

9. Raster Output 단계에선 L2 캐쉬의 데이터를 VRAM의 Framebuffer로 옮긴다.

반응형