Unreal Engine의 PSO 관련 정리
PSO 캐싱 수집 및 빌드 과정 예시
Wiki topics:
🎮 · Gaming
Unreal Engine의 PSO 관련 정리
PSO 캐싱 수집 및 빌드 과정 예시
사전 준비 사항
- 예제 다운로드 : ActionRPG_buildPSO_scripts.zip
- 주의: 사용시 엔진 설치 위치 및 프로젝트 경로 수정 필요함
- 안드로이드 디바이스의 경우 개발자 모드로 USB 디버깅을 활성화 해서 USB 로 연결을 통해 adb 명령어 실행이 가능해야 함
PSO 수집, 단계별 진행 과정
PSO 수집 및 적용 과정 도식화 (출처 : https://imzlp.com/posts/24336/)
- config 폴더에서 PSO 수집용 .ini 설정

플랫폼별 프로파일 설정 예시
bShareMaterialShaderCode=True
bSharedMaterialNativeLibraries=True
[DevOptions.Shaders]
NeedsShaderStableKeys=true
r.ShaderPipelineCache.Enabled=1
r.ShaderPipelineCache.LogPSO=1
r.ShaderPipelineCache.SaveBoundPSOLog=1
r.ShaderPipelineCache.StartupMode=1
r.ShaderPipelineCache.SetBatchMode=Fast
; 기본적으로 사전 캐시가 불가능한 PSO만 캡처하는 라이트 모드입니다.
; (아래 3개의 CMD는 번들된 PSO만 사용하려면 지금은 생략해도 됩니다!)
r.ShaderPipelineCache.ExcludePrecachePSO=1
; -logPSO 중에 어떤 PSO를 생략할 수 있는지 확인하려면 Validation이 필요합니다.
r.PSOPrecache.Validation=2
; 위 두 설정은 프리캐싱을 사용하는 경우에만 관련 있습니다.
r.PSOPrecaching=1
r.PSOPrecache.ProxyCreationWhenPSOReady=1
- 위 ini 으로 PSO 수집용 빌드를 만들면 아래 경로에
.shk파일들 생성 4.27 이전 버전에서는 파일 확장자가scl.csv이었음Saved\Cooked\WindowsNoEditor\ActionRPG\Metadata\PipelineCaches\

안정적인 PSO 목록 (scl.csv) 정의
- -logPSO 실행 인자 사용 또는 .ini 설정을 통해서 PSO 수집용 빌드를 실행 후 게임 플레이해서 PSO 기록 Windows 기준, 아래 위치에 rec.upipelinecache 파일들이 수집 됨 Saved\StagedBuilds\WindowsNoEditor\ActionRPG\Saved\CollectedPSOs\
- 위에서 얻은 PipelineCaches 및 CollectedPSOs 파일들을 모아서 stablepc.csv 로 변환 후 build/windows 폴더로 복사 사전 캐싱이 활성화된 상태에서 따라왔다면 이 시스템은 기본적으로 다음 CVAR을 사용하여 사전 캐싱으로 처리되지 않는 PSO만 캡처하는 “가벼운” 모드로 실행됩니다.
- 마지막으로 stablepc.csv를 포함해서 다시 패키징 하면 빌드에 PSO 포함되고 로그에 다음과 같이 포함된 PSO 개수를 확인이 가능함

빌드에 포함된 PSO 개수 확인

셰이더 링크에 소요된 시간 확인 (출처:https://imzlp.com/posts/24336/)
PSO 캐시 관련 파일 종류
- Stable Shader Key → PSO → Binary 캐시 순으로 매핑
**.shk(UE4.27 이전은scl.csv) : **Stable Shader Key 정보 저장, DefaultEngine.ini 에서 NeedsShaderStableKeys=true 으로 생성 ShaderCodeLibrary.cpp#L65**.rec.upipelinecache: **런타임에서 기록된 PSO.**.stablepc.csv:** 쿠킹시 생성, 안정적인 PSO 목록 정의 (엑셀로 확인 가능) DefaultEngine.ini 에서 -ShaderPipelineCache.SaveBoundPSOLog=1 사용**.upipelinecache: **바이너리 PSO 캐시 (런타임 로딩용).**.spc: PSO** 정보를 바이너리 형태로 저장한 런타임용 캐시 파일
게임에서의 .ini 설정
; DefaultEngine.ini or DefaultGameUserSettings.ini 등에서 설정 가능
[DevOptions.Shaders]
NeedsShaderStableKeys=true
[/Script/Engine.RendererSettings]
r.PSOPrecaching=1
; 'stat psocache', Insights에서 검증을 위해 활성화 필요,
; ExcludePrecachePSO cvar도 이 설정이 필요함
r.PSOPrecache.Validation=2
; "stat psocache" 로그에 더 많은 디테일을 표시함
r.PSOPrecache.Validation.TrackMinimalPSOs=1
; 아래 설정들은 PSO Precache와 함께 번들 PSO 단계들을 결합하기 위한 설정임
r.ShaderPipelineCache.ExcludePrecachePSO=1
r.ShaderPipelineCache.Enabled=1
; 메인 메뉴에서 "히치 없는(hitchless)" 실행을 가능하게 함 (선택 사항)
; 0: 사전 컴파일이 일시 중지되고 ResumeBatching()을 호출할 때까지 아무것도 컴파일되지 않습니다.
; 1: '빠른' 모드에서 사전 컴파일이 활성화됩니다.
; 2: '백그라운드' 모드에서 사전 컴파일이 활성화됩니다.
r.ShaderPipelineCache.StartupMode=2
;FirstToLatestUsed = 1, // 가장 낮은 첫 번째 프레임이 사용된 PSO부터 시작하여 가장 높은 프레임이 사용된 PSO 순으로 작업합니다.
;MostToLeastUsed = 2 // 가장 자주 사용되는 PSO부터 시작하여 가장 적게 사용되는 PSO 순으로 작업합니다.
r.ShaderPipelineCache.LRU같은 CVars를 사용해 PSO 캐싱 정책을 조정할 수 있습니다.- PSO 캐시가 없거나 부족하면 게임 실행 중에 PSO를 동적으로 컴파일하며, 이때 성능 저하가 발생할 수 있습니다.
- 따라서 PSO 캐시를 잘 관리하고 미리 컴파일하는 것이 중요합니다.
관련 콘솔 명령어
r.PSOPrecache=1PSO 프리캐싱 활성화 (런타임 시 PSO 캐시 로딩 및 수집).r.PSOPrecache.Validation=2PSO 프리캐시 로그와 검증 레벨 조절r.ShaderPipelineCache.Enabled=1셰이더 파이프라인 캐시 활성화.r.ShaderPipelineCache.LogPSO=1PSO 로그 활성화 (PSO 수집 시 기록).r.ShaderPipelineCache.SaveUserCache플레이 중 생성된 PSO를 저장.r.ShaderPipelineCache.StartupMode 0: 비활성화1: Background Precompile (기본값)3: Precompile before render 시작r.ShaderPipelineCache.Open원하는 파일 이름을 가져와서 파이프라인 파일 캐시를 로드r.ShaderPipelineCache.Save현재 파이프라인 파일 캐시를 저장합니다.r.ShaderPipelineCache.Close현재 파이프라인 파일 캐시를 닫습니다.r.ShaderPipelineCache.SetBatchMode컴파일 배치 모드를 설정합니다. 다음 중 하나여야 합니다. Pause: 사전 컴파일을 일시 중지합니다. Background: 우선순위가 낮은 사전 컴파일입니다. Fast: 우선순위가 높은 사전 컴파일입니다.
PSO 관련 주요 코드 요약
// Runtime/Execution/Private/LaunchEngineLoop.cpp 코드 요약
// 런타임 셰이더 요청 시 라이브러리에서 먼저 조회 → 없으면 컴파일 후 엔트리 추가
// Exit 단계에서 신규 엔트리(HasNewEntries)가 있으면 디스크에 저장
bool bUseShaderCodeLibrary; // 콘솔 변수 r.ShaderCodeLibrary.Enable
bool bRebuildShaderCodeLibrary; // 콘솔 변수 r.ShaderCodeLibrary.Rebuild
int32 FEngineLoop::PreInitPreStartupScreen( const TCHAR* CmdLine)
{
// cook.AllowCookedDataInEditorBuilds 변수로 에디터에서 PSO 사용 여부 확인
// 라이브러리 초기화 단계에서 PSO 캐시(.scl.bin/.csv)를 로드
bool bUseCodeLibrary = FPlatformProperties:: RequiresCookedData () || GAllowCookedDataInEditorBuilds;
if (bUseCodeLibrary)
{
// 패키징된 프로젝트의 머티리얼 셰이더 코드 라이브러리 오픈
// 콘텐츠 디렉터리에 있는 글로벌 셰이더 라이브러리만 열립니다.
FShaderCodeLibrary:: InitForRuntime (GMaxRHIShaderPlatform);
// 파이프라인 캐시 시스템 초기화 (실제 오픈은 아래에서 수동 호출)
FShaderPipelineCache::Initialize(GMaxRHIShaderPlatform);
}
// EarlyStartupScreen.. (코드 생략)
// EarlyStartupPatch 후에 새 config 다시 로드
HandleConfigReload(bWithConfigPatching);
// EarlyStartupPatch후에 대상 플랫폼별 셰이더 레지스트리 조회
FShaderCodeLibrary::OpenLibrary (FApp:: GetProjectName (), FPaths:: ProjectContentDir ()); ( const FString& RootDir : FPlatformMisc:: GetAdditionalRootDirectories ( )) { FShaderCodeLibrary:: OpenLibrary (FApp:: GetProjectName (), FPaths:: Combine (RootDir, FApp:: GetProjectName (), TEXT ( "Content" ))); }
// 이제 셰이더 코드 메인 라이브러리가 열렸습니다. 이미 초기화된 경우 사전 컴파일을 시작합니다.
FShaderPipelineCache::OpenPipelineFileCache(GMaxRHIShaderPlatform)
// RHI/Private/PipelineFileCache.cpp 코드 요약
// 세 함수 모두 최초 호출 시 인자를 한 번만 파싱하여 bCmdLineForce에 저장
// 이후에는 런타임 콘솔 변수(CVar…) 또는 커맨드라인 플래그에 의해 동작을 켜고 끔
// 파일 캐시 활성화 여부 리턴
// -psocache 커맨드라인 인자 와 r.PSOFileCacheEnabled 콘솔 변수 사용
bool FPipelineFileCache::IsPipelineFileCacheEnabled ()
// PSO를 캐시에 기록(Log)할지를 결정
// -logpso 커맨드라인 인자 와 r.PSOFileCacheLogPSO 콘솔 변수 사용
bool FPipelineFileCache::LogPSOtoFileCache()
// 새로 생성된 PSO를 보고할지 결정
// -reportpso 커맨드라인 인자 와 r.PSOFileCacheReportPSO 콘솔 변수 사용
bool FPipelineFileCacheManager::ReportNewPSOs()
// UnrealEd\Private\CookOnTheFlyServer.cpp 코드 요약
// 이 함수는 지정된 타겟 플랫폼과 셰이더 포맷에 대해
// 쿠킹 과정에서 .spc(바이너리) .csv(텍스트)를 토대로
// Stable Pipeline Cache(SPC) 파일을 생성하기 위한 커맨드렛을 실행하고
// 해당 파일을 Metadata 디렉토리로 복사하는 작업을 수행합니다.
void UCookOnTheFlyServer::CreatePipelineCache()
{
// 1. 대상 플랫폼에 대한 Stable Shader Key CSV 파일 목록 조회
// 2. 기존 Pipeline Cache 파일 검색 (*.spc 또는 4.27 이전용 *.stablepc.csv)
// 3. 파일 검색 성공시, 파이프라인 캐시 생성 절차 진행
// 4. UShaderPipelineCacheToolsCommandlet 실행
// 5. 생성된 .upipelinecache 파일을
// 디렉토리(GetMetadataDirectory()/PipelineCaches) 로 복사
}
// Runtime\RenderCore\Private\ShaderPipelineCache.cpp
bool FShaderPipelineCache::OpenPipelineFileCache(EShaderPlatform Platform)
{
if (!bFileOpen)
{
bFileOpen = OpenPipelineFileCache (FApp:: GetProjectName (), Platform);
}
}
PSO 캐싱 (PSO Caching) — 언리얼 엔진 (문서 요약)
셰이더 스터터링(Shader Stuttering)이란?
셰이더 스터터링의 원인
- GPU 셰이더는 HLSL 등의 고급 언어로 작성됨
- 최종적으로는 GPU 전용 머신 코드로 변환되어야 함
- 드라이버가 런타임에 바이트코드를 GPU 머신 코드로 컴파일
- 컴파일 지연이 렌더링 차단 -> 스터터링 발생
문제 원인 : CPU vs GPU 컴파일 차이
- CPU: 플랫폼 간 호환성 높음 (x64 등)
- GPU: 공급업체별/세대별 인스트럭션 세트 상이
- GPU 셰이더는 바이트코드(SPIR-V, DXIL 등)로 배포하고 런타임에 변환
이전 방법: 번들화된 PSO 캐시
- 플레이 테스트나 자동 재생 등을 통해 PSO를 수집하고 빌드에 포함.
- 제한: 콘텐츠 변경 시 캐시 업데이트 필요.
- 사용자 제작 콘텐츠나 동적 머티리얼에는 적용 어려움.
- 캐시 수집에 많은 리소스 요구.
새로운 해결 방법: PSO 프리캐싱 (UE 5.2+)

PSO 프리캐싱
- 오브젝트 로딩 시점에 해당 메시, 머티리얼, 설정 등을 기준으로 잠재적 PSO 서브셋을 예측. 이 서브셋만 미리 컴파일해서 로딩 중에 준비함.
- 예: 포트나이트에서는 전체 조합 수백만 개 중 약 3만 개 PSO만 컴파일 → 실제 사용은 1만 개 정도.
실제 적용 방식 :
- 로딩 중 PSO 프리캐싱: 대표적 상황(레벨 로딩, 캐릭터 생성 등)에서 예측된 PSO를 컴파일.
- 런타임 캐싱: 스트리밍 중 생성되는 콘텐츠도 화면 표시 전 PSO 컴파일 → 필요시 기본 fallback 머티리얼로 대체
- 스트리밍 오브젝트/머티리얼이 등장 시 PSO 미리 준비
- 대부분의 경우 1~2 프레임 지연으로 사용자 눈에는 인지되지 않음
PSO 프리캐싱 개발자 모범 사례
- 필수 요소들에 대해 기존 PSO 번들링, 로딩타임에 프리캐싱 병행 사용
- 플레이테스트, 자동화된 레벨 플라이스루 통해 PSO 수집
- 가능한 한 초기 로딩 중 PSO를 생성하도록 설계
- 콘텐츠 팀과 협력해 PSO 수 변화 추적 및 최적화
- 디버깅 시
r.ShaderPipelineCache.*등의 콘솔 명령어 활용.
향후 개선 방향
- PSO 프리캐싱 정확도 향상
- 사용되지 않은 PSO 예측 제거
- 사용자 제작 콘텐츠에 대한 캐시 생성 자동화
- PSO 메모리 사용량 최소화
1. PSO란?
- PSO는 Pipeline State Object의 약자로, 그래픽 파이프라인의 모든 상태를 포함하는 객체입니다.
- PSO는 셰이더, 블렌드 상태, 래스터라이저 설정 등 GPU 렌더링에 필요한 모든 정보를 미리 설정해 둔 상태 객체입니다.
- GPU는 PSO를 기반으로 렌더링 명령을 수행하며, PSO가 바뀔 때마다 GPU 내부 상태를 변경합니다.

PSO 생성 및 적용 과정
2. PSO 생성 비용과 문제점
- PSO를 처음 생성하거나 변경할 때 GPU 드라이버에서 많은 비용이 소요됩니다.
- 특히 모바일이나 콘솔 같은 제한된 하드웨어에서 PSO 생성 비용은 게임 성능에 큰 영향을 미칩니다.
- PSO가 많아질수록 PSO 캐시(내부 또는 디스크 캐시) 관리가 복잡해지고, 드라이버에서 이를 처리하는 시간도 늘어납니다.
3. 번들화된 PSO 캐싱의 필요성
- PSO 캐싱은 게임 실행 전에 PSO를 미리 컴파일하여 저장해두는 것을 의미합니다.
- 이렇게 하면 실행 중에 PSO 생성 비용으로 인한, 프레임 드롭과 히치(hitch)를 감소시킬 수 있습니다.
- 언리얼 엔진은 PSO 캐시를 디스크에 저장하고, 게임 시작 시 또는 로딩 중에 미리 로드하는 기능을 제공합니다.
4. 언리얼 엔진의 PSO 캐싱 지원
- 언리얼 엔진 4.22부터 PSO 캐싱 기능이 도입되었으며, 이후 점점 개선되어 왔습니다.
- 엔진 내부에 PSO를 저장하는 시스템이 있고, PSO 캐시를 만드는 도구를 제공하여 PSO 목록을 수집하고 프리컴파일할 수 있습니다.
5. PSO 캐싱 구현 방법
- PSO 수집(PSO Collection): 게임을 실행하면서 사용하는 PSO를 기록하여 목록을 만듭니다. 이 목록은 PSO 캐시 생성 시 사용됩니다.
- PSO 프리컴파일(PSO Precompile): 수집된 PSO 목록을 기반으로 모든 PSO를 미리 GPU 드라이버에 컴파일하도록 강제합니다. 이 과정은 보통 빌드 파이프라인 또는 초기 로딩 시 수행됩니다.
- PSO 캐시 저장 및 로드: 프리컴파일된 PSO 데이터를 디스크에 저장하고, 이후 게임 실행 시 캐시를 빠르게 불러옵니다.
6. PSO 캐시 최적화 팁
- 필요한 PSO만 수집: 가능한 한 많이 PSO를 사용하지 않는 것이 중요합니다. 불필요한 머티리얼, 셰이더, 렌더링 설정을 줄여야 합니다.
- 멀티플 플랫폼 관리: 플랫폼별 PSO 캐시는 다르게 생성되어야 하며, 각각 별도로 관리해야 합니다.
- 캐시 크기 제한: 너무 큰 PSO 캐시는 로딩 시간을 증가시키므로 적절한 크기로 제한하는 것이 좋습니다.
PSO 생성 및 캐시 저장 과정 분석
- PSO 요청 시:
FShaderPipelineCache::FindOrAddPSO가 호출되어, 기존 캐시에 PSO가 있으면 반환. 없으면 새로운 PSO 생성 후 캐시에 저장. - LRU 갱신: PSO가 사용될 때마다 해당 PSO의 사용 시점 (timestamp 또는 frame count)을 갱신. LRU 리스트에서 이 PSO의 위치를 가장 최근 사용 항목으로 갱신(앞쪽으로 이동).
LRU 캐시 크기 제한 및 PSO 제거
- Unreal Engine 내
r.ShaderPipelineCache.LRU.CacheSizeCVar 값으로 캐시 최대 크기 제한을 설정. - PSO 캐시 저장 시, 저장된 PSO가 이 크기를 초과하면:
- LRU 리스트의 맨 뒤(가장 오랫동안 사용 안 한 PSO) 부터 순차적으로 삭제.
- 삭제 시 해당 PSO 객체를 GPU에서 해제하고 메모리에서 제거.
- 이후 새로운 PSO가 추가되면 캐시 크기는 다시 관리됨.
PSO 저장 및 세션 관리
- Unreal은 세션 종료 시, 현재 사용중인 PSO들을 디스크에 저장하여 다음 실행 시 재사용 가능.
- PSO가 LRU 정책에 의해 제거된 경우, 재사용 시 다시 생성 또는 디스크에서 로드.
References :
메타데이터
- post_id
- 0f89181be5f3
- slug
- unreal-engine의-pso-관련-정리-0f89181be5f3
- url
- https://medium.com/@3devnote/unreal-engine%EC%9D%98-pso-%EA%B4%80%EB%A0%A8-%EC%A0%95%EB%A6%AC-0f89181be5f3
- canonical_url
- https://medium.com/@3devnote/unreal-engine%EC%9D%98-pso-%EA%B4%80%EB%A0%A8-%EC%A0%95%EB%A6%AC-0f89181be5f3
- author_url
- https://medium.com/@3devnote
- status
- ok
- fetched_at
- 2026-07-19 16:50:09