Quick Flow
scene data -> command recording / resource binding
-> vertex shader -> primitive setup / rasterization
-> fragment shader -> depth, stencil, blend
-> color / depth attachments -> later passes or display| 경계 | 입력 | 출력 | 문제가 생기면 |
|---|---|---|---|
| CPU / GPU | mesh, material, PSO, resource binding | 제출된 draw 또는 dispatch | draw 수와 state change가 많아 CPU가 먼저 막힘 |
| Vertex | vertex/index buffer, uniform | clip-space position, varyings | layout이나 transform이 틀려 geometry가 깨짐 |
| Raster / fragment | primitive, varyings, texture | fragment output | 화면 점유율과 texture fetch가 커져 fill-rate가 막힘 |
| Output merger | fragment color, depth/stencil state | color/depth attachment | depth compare, blend, target format 불일치 |
| 다음 pass | attachment와 resource state | sampled input 또는 display | read/write 순서나 layout이 어긋남 |
단계별 책임
렌더링 파이프라인은 3D 장면을 2D 화면 이미지로 바꾸는 일련의 단계입니다. CPU는 어떤 메시를 어떤 상태로 그릴지 명령을 만들고, GPU는 수많은 정점과 fragment를 병렬로 처리합니다.
CPU setup에서는 draw call, pipeline state, bind group이나 descriptor, vertex/index buffer, uniform 같은 입력을 묶습니다. 이 단계가 정리되어야 GPU가 어떤 shader로 어떤 geometry를 어떤 render target에 그릴지 알 수 있습니다. GPU가 실행할 때까지 CPU는 command를 기록할 뿐이고, resource가 현재 읽기/쓰기 중인지와 in-flight frame에서 덮어써도 되는지도 별도 규약으로 관리합니다.
vertex shader 이후에는 primitive assembly가 정점들을 삼각형, 선, 점 같은 primitive로 묶습니다. rasterization은 이 primitive가 화면의 어떤 sample을 덮는지 계산하고, fragment shader는 그 sample 후보마다 색이나 material 값을 계산합니다.
output merger는 fragment 결과를 그대로 저장하지 않습니다. depth test와 stencil test로 남길지 버릴지 판단하고, 기존 color attachment와 blending한 뒤 framebuffer에 기록합니다. transparent draw는 기존 color를 읽어 합성하므로 보통 opaque depth가 끝난 뒤 정렬해 처리하며, depth write 여부와 blend 식을 함께 정해야 합니다.
중요한 구분은 정점 단계는 기하 정보, fragment 단계는 화면 샘플 후보를 다룬다는 점입니다. 정점이 많으면 vertex shader 비용이 커지고, 화면을 많이 덮으면 fragment shader와 fill-rate 비용이 커집니다.
정점이 많음 -> vertex 처리 비용
큰 면이 겹침 -> rasterization 이후 fragment 비용
투명/후처리 -> blending, bandwidth 비용고정 기능과 프로그래머블 단계
현대 GPU 파이프라인은 일부 단계가 고정 기능이고, 일부 단계는 shader로 작성합니다. vertex shader와 fragment shader는 대표적인 프로그래머블 단계입니다. 반면 rasterization, depth test, blending은 설정은 가능하지만 기본 동작 자체는 고정 기능에 가깝습니다.
Pass 사이의 계약
| 앞 pass가 만든 것 | 뒤 pass가 요구하는 것 | 꼭 맞출 규약 |
|---|---|---|
| Depth target | SSAO, depth of field, fog, shadow compare | projection depth range, normal/reversed Z, linearization |
| G-buffer normal | lighting, reflection, denoiser | world/view/tangent space와 encode format |
| HDR color | bloom, tone mapping, color grading | linear HDR format, exposure 적용 위치 |
| Motion vector | TAA, motion blur, temporal denoise | 방향, 단위, jitter 제거 여부 |
| MSAA color | post-process | resolve 시점과 sample count |
파이프라인은 그림처럼 한 번만 아래로 흐르는 단일 shader가 아닙니다. shadow, depth prepass, opaque, transparency, post-process, UI가 각각 attachment를 읽고 쓰는 여러 pass로 구성됩니다. 위 표의 규약이 pass마다 다르면 단계 하나만 정상이어도 최종 화면에는 depth inverted, normal seam, ghosting처럼 보입니다.
병목 읽기
| 증상 | 먼저 의심할 곳 |
|---|---|
| 정점 수 증가에 따라 느림 | vertex processing |
| 해상도 증가에 따라 느림 | fragment, fill-rate, bandwidth |
| draw call이 많을수록 느림 | CPU submission, state change |
| 투명 물체가 많을수록 느림 | overdraw, blending |
| 복잡한 재질에서 느림 | fragment shader, texture sampling |
성능 문제를 볼 때 “GPU가 느리다”로 뭉뚱그리면 원인을 찾기 어렵습니다. 정점 수, 화면 점유율, 텍스처 샘플 수, blending, 메모리 대역폭 중 어떤 축이 늘었는지를 분리해야 합니다. 해상도만 낮춰 크게 빨라지면 fragment/fill-rate 후보이고, draw를 묶었을 때 CPU frame time만 줄면 submission 후보입니다.
디버깅 출발점
frame capture에서 한 draw 또는 pass를 선택해 vertex input, pipeline state, shader resource, attachment, output을 순서대로 확인하십시오. "shader가 틀렸다"는 결론 전에 실제로 바인딩된 texture mip, depth clear, viewport, blend/depth state가 의도와 같은지 보는 편이 빠릅니다.
참고 링크
2 sources