쉐이더 파이프라인의 구조와 캐싱 개념
쉐이더(Shader)는 GPU가 화면을 렌더링하기 위해 실행하는 작은 프로그램이다.
버텍스, 픽셀, 지오메트리, 컴퓨트 등 다양한 스테이지가 결합해 하나의 파이프라인을 구성한다.
문제는 이 파이프라인을 매번 새로 생성하면 GPU와 드라이버가 막대한 비용을 소모한다는 점이다.
이를 해결하기 위해 도입된 것이 쉐이더 파이프라인 캐싱(Shader Pipeline Caching)이다.
엔진은 GPU 드라이버가 생성한 파이프라인 객체를 내부 데이터베이스에 저장하고,
동일한 설정이 다시 요청되면 캐시된 객체를 재사용한다.
이 과정에서 중요한 키는 파이프라인 해시(Pipeline Hash)다.
파이프라인 상태(블렌딩, 깊이, 텍스처 등)를 해시로 변환해 동일한 구성이 감지되면 즉시 캐시에서 불러온다.
예를 들어, 동일한 머티리얼을 사용하는 100개의 메시는 단 하나의 파이프라인 객체만 생성한다.
이를 통해 드로우콜 병합이 가능해지고, GPU의 상태 전환 비용이 대폭 감소한다.
파이프라인 캐싱은 CPU-GPU 사이의 불필요한 드라이버 호출을 줄이는 효과를 갖는다. DirectX 12와 Vulkan 모두 파이프라인 캐시를 지원하며, 엔진은 이를 직렬화해 로컬 파일 또는 클라우드로 저장한다. 이를 통해 동일한 그래픽 설정에서 실행할 때, 첫 프레임 로딩 속도를 수 초 단축할 수 있다.
컴파일 병렬화와 멀티스레드 파이프라인 빌드
쉐이더 컴파일은 GPU 실행을 위한 전처리 과정으로,
대규모 프로젝트에서는 수천 개의 셰이더를 동시에 빌드해야 한다.
이때 단일 스레드로 처리하면 빌드 시간이 수십 분 이상 걸린다.
이를 해결하기 위해 컴파일 병렬화(Parallel Shader Compilation) 구조가 사용된다.
병렬 컴파일러는 각 셰이더 소스 파일을 독립적으로 분할하고,
멀티코어 CPU에 작업을 분배한다.
각 코어는 프리프로세싱, 최적화, 코드 변환, GPU ISA 생성 단계를 독립적으로 수행하며,
모든 결과를 병합해 최종 캐시로 저장한다.
이 방식은 CPU 활용률을 90% 이상으로 끌어올리며,
컴파일 시간은 최대 70% 단축된다.
또한, 엔진은 셰이더 컴파일 로그를 데이터베이스로 관리한다.
어떤 셰이더가 실패했는지, 어떤 플랫폼에서 병목이 발생했는지를 추적해,
빌드 이후에도 자동 재컴파일을 수행한다.
이를 통해 “불필요한 전체 리빌드”를 방지하고,
개발 중 GPU 파이프라인의 불안정성을 최소화한다.
특히 Unreal Engine 5나 Unity HDRP는 셰이더 컴파일러를 “비동기 작업(Task Graph)” 형태로 처리한다. 즉, CPU의 다른 스레드가 렌더링을 수행하는 동안에도 컴파일이 병렬로 진행된다. 이렇게 하면 로딩 화면 중에도 GPU 캐시 빌드가 가능해, 게임 실행 후 첫 씬 로딩 시간을 크게 줄인다.
GPU 스테이트 관리 효율화와 동적 캐싱 전략
GPU의 스테이트(State)는 렌더링 파이프라인의 설정값 집합이다.
이 설정이 자주 변경되면 GPU 내부 캐시가 무효화되어 성능이 급격히 하락한다.
이를 방지하기 위해 엔진은 스테이트 관리 효율화(State Management Optimization) 기법을 사용한다.
가장 대표적인 방식은 “State Sorting”이다.
즉, 드로우콜을 렌더링하기 전에 스테이트 변경 빈도가 낮은 순서대로 정렬하는 것이다.
예를 들어, 동일한 셰이더와 텍스처를 사용하는 오브젝트를 연속으로 배치하면,
GPU가 동일한 파이프라인을 유지할 수 있어 상태 전환 비용이 줄어든다.
두 번째는 “Dynamic Pipeline Caching”이다.
엔진은 프레임 실행 중 캐시 미스를 감지하고,
해당 파이프라인을 백그라운드에서 비동기 재생성한다.
이렇게 하면 플레이 도중에도 렌더링 끊김이 발생하지 않는다.
마지막으로, 스테이트 캐시는 프레임 단위가 아니라
리소스 단위로 관리된다.
GPU 리소스가 업데이트될 때만 캐시를 재검증하므로,
매 프레임 캐시 검사가 필요 없어진다.
이 최적화만으로 GPU 파이프라인의 평균 효율이 10~20% 향상된다.