← Back to list

Unreal Engine의 PSO 관련 정리

PSO 캐싱 수집 및 빌드 과정 예시

UnrealStudy · 2025-05-29 03:56 · 0 claps · 17.5 min read
#unreal-engine #optimization
Open on Medium ↗
Wiki topics: 🎮 · Gaming

Unreal Engine의 PSO 관련 정리

PSO 캐싱 수집 및 빌드 과정 예시

사전 준비 사항

  • 예제 다운로드 : ActionRPG_buildPSO_scripts.zip
  • 주의: 사용시 엔진 설치 위치 및 프로젝트 경로 수정 필요함
  • 안드로이드 디바이스의 경우 개발자 모드로 USB 디버깅을 활성화 해서 USB 로 연결을 통해 adb 명령어 실행이 가능해야 함

PSO 수집, 단계별 진행 과정

PSO 수집 및 적용 과정 도식화 (출처 : https://imzlp.com/posts/24336/)

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) 정의

안정적인 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 개수 확인

빌드에 포함된 PSO 개수 확인

셰이더 링크에 소요된 시간 확인 (출처:https://imzlp.com/posts/24336/)

셰이더 링크에 소요된 시간 확인 (출처: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=1 PSO 프리캐싱 활성화 (런타임 시 PSO 캐시 로딩 및 수집).
  • r.PSOPrecache.Validation=2 PSO 프리캐시 로그와 검증 레벨 조절
  • r.ShaderPipelineCache.Enabled=1셰이더 파이프라인 캐시 활성화.
  • r.ShaderPipelineCache.LogPSO=1 PSO 로그 활성화 (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) — 언리얼 엔진 (문서 요약)

https://www.unrealengine.com/ko/tech-blog/game-engines-and-shader-stuttering-unreal-engines-solution-to-the-problem

셰이더 스터터링(Shader Stuttering)이란?

셰이더 스터터링의 원인

  • GPU 셰이더는 HLSL 등의 고급 언어로 작성됨
  • 최종적으로는 GPU 전용 머신 코드로 변환되어야 함
  • 드라이버가 런타임에 바이트코드를 GPU 머신 코드로 컴파일
  • 컴파일 지연이 렌더링 차단 -> 스터터링 발생

문제 원인 : CPU vs GPU 컴파일 차이

  • CPU: 플랫폼 간 호환성 높음 (x64 등)
  • GPU: 공급업체별/세대별 인스트럭션 세트 상이
  • GPU 셰이더는 바이트코드(SPIR-V, DXIL 등)로 배포하고 런타임에 변환

이전 방법: 번들화된 PSO 캐시

  • 플레이 테스트나 자동 재생 등을 통해 PSO를 수집하고 빌드에 포함.
  • 제한: 콘텐츠 변경 시 캐시 업데이트 필요.
  • 사용자 제작 콘텐츠나 동적 머티리얼에는 적용 어려움.
  • 캐시 수집에 많은 리소스 요구.

새로운 해결 방법: PSO 프리캐싱 (UE 5.2+)

PSO 프리캐싱

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 생성 및 적용 과정

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.CacheSize CVar 값으로 캐시 최대 크기 제한을 설정.
  • 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