UE项目优化:PSO Cache

UE project optimization: PSO Cache

UE中具有PSO Cache机制,全称Pipeline State Object Caching,用于预先记录和构建出运行时所使用的材质依赖的Shader信息,当项目首次使用这些Shader时,该列表可以加速Shader的加载/编译过程。PSO Cache会把渲染状态、顶点声明、Primitive类型、RenderTarget像素格式等数据保存到文件中,提升Shader的加载效率。
本篇文章主要介绍PSO Cache的启用及构建流程,并会分析PSO Cache在引擎中的加载流程以及实现热更PSO方式、错误处理等,PSO Cache的原理有时间再进行详细分析。

PSO Cache的官方文档:PSO Cache

PSO Cache构建流程概览:

PSO Cache的部署和使用大致分为以下几个步骤:

  1. 为项目开启PSO Cahche和ShaderStableKeys,打包后可以从Metadata/PipelineCaches目录下获得ShaderStableInfo*.scl.csv
  2. 添加logPSO参数启动游戏,用于在运行时记录PSO数据(*.rec.upipelinecache
  3. 通过ShaderStableInfo*.scl.csv*.rec.upipelinecache生成*.stablepc.csv
  4. 再次执行Cook,通过*.stablepc.csv生成upipelinecache文件,打至包内;
  5. 启动游戏,引擎自动加载*.stable.upipelinecache,编译Shader时使用PSO Cache

本篇文章的内容顺序也遵循着几个步骤。

启用ShaderStableKeys

首先需要为项目开启ShaderStableKeys,在执行Cook时生成稳定的ShaderKey,作为记录Shader的凭据。

DefaultEngine.ini(或平台相关如AndroidEngine.ini)中添加以下值:

1
2
[DevOptions.Shaders]
NeedsShaderStableKeys=true

添加之后再执行打包(Cook),会创建以下目录:

1
Saved/Cooked/PLATFORM_NAME/PROJECT_NAME/Metadata/PipelineCaches

并且会在该目录下生成两个文件(分别对应项目、引擎):

1
2
ShaderStableInfo-PROJECT_NAME-GLSL_ES3_1_ANDROID.scl.csv
ShaderStableInfo-Global-GLSL_ES3_1_ANDROID.scl.csv

Cook过程中会有以下log,表明生成了这两个文件:

1
2
LogCook: Display: Saved scl.csv D:/PSOExample/Saved/Cooked/Android_ASTC/PSOExample/Metadata/PipelineCaches/ShaderStableInfo-Global-GLSL_ES3_1_ANDROID.scl.csv for platform Android_ASTC
LogCook: Display: Saved scl.csv D:/PSOExample/Saved/Cooked/Android_ASTC/PSOExample/Metadata/PipelineCaches/ShaderStableInfo-PSOExample-GLSL_ES3_1_ANDROID.scl.csv for platform Android_ASTC

可以使用它们通过-run=ShaderPipelineCacheTools这个Commandlet来生成*.stablepc.csv

*.scl.csv文件的内容:

运行时捕获PSO数据

启动游戏时加入-logPSO参数或者在DefaultEngine.ini中加入以下配置:

Config/DefaultEngine.ini
1
2
3
4
[ConsoleVariables]
r.ShaderPipelineCache.Enabled=1
r.ShaderPipelineCache.LogPSO=1
r.ShaderPipelineCache.SaveBoundPSOLog=1

也可以在Devices Profile中设置:

这两个参数在以下代码中使用:

Runtime/RHI/Private/PipelineFileCache.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
bool FPipelineFileCache::IsPipelineFileCacheEnabled()
{
static bool bOnce = false;
static bool bCmdLineForce = false;
if (!bOnce)
{
bOnce = true;
bCmdLineForce = FParse::Param(FCommandLine::Get(), TEXT("psocache"));
UE_CLOG(bCmdLineForce, LogRHI, Warning, TEXT("****************************** Forcing PSO cache from command line"));
}
return FileCacheEnabled && (bCmdLineForce || CVarPSOFileCacheEnabled.GetValueOnAnyThread() == 1);
}

bool FPipelineFileCache::LogPSOtoFileCache()
{
static bool bOnce = false;
static bool bCmdLineForce = false;
if (!bOnce)
{
bOnce = true;
bCmdLineForce = FParse::Param(FCommandLine::Get(), TEXT("logpso"));
UE_CLOG(bCmdLineForce, LogRHI, Warning, TEXT("****************************** Forcing logging of PSOs from command line"));
}
return (bCmdLineForce || CVarPSOFileCacheLogPSO.GetValueOnAnyThread() == 1);
}

运行游戏时会有log:

1
2
3
4
5
6
7
8
9
10
11
12
LogConfig: Applying CVar settings from Section [ConsoleVariables] File [../../../FGame/Saved/Config/Android/Engine.ini]
LogConfig: Setting CVar [[r.ShaderPipelineCache.Enabled:1]]
LogConfig: Setting CVar [[r.ShaderPipelineCache.LogPSO:1]]
LogConfig: Setting CVar [[r.ShaderPipelineCache.SaveBoundPSOLog:1]]
...
LogRHI: Base name for record PSOs is ../../../FGame/Saved/CollectedPSOs/++UE4+Release-4.25-CL-0-FGame_GLSL_ES3_1_ANDROID_00084A4308D90436AC0F652223AA8D4F.rec.upipelinecache
LogRHI: Could not open FPipelineCacheFile: ../../../FGame/Content/PipelineCaches/Android/FGame_GLSL_ES3_1_ANDROID.upipelinecache
...
LogRHI: Display: Encountered a new graphics PSO: 3478445130
LogRHI: Display: New Graphics PSO (3478445130) Description: 416F798F22743F626513A16205A15ECB135C3791,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,<0 1 0 0 1 0 0 0 1 0 0 1 0 15 0 1 0 0 1 0 15 0 1 0 0 1 0 15 0 1 0 0 1 0 15 0 1 0 0 1 0 15 0 1 0 0 1 0 15 0 1 0 0 1 0 15 0>,<0.000000 0.000000 2 1 0 0>,<0 3 0 7 0 0 0 0 7 0 0 0 255 255>,1,11,1051148,2,2,0,0,0,4,10,1114633,0,0,18,2569,0,0,37,1051145,0,0,37,2585,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15360,15391,1055,1027,1025,0,0,0,0,1,<0 0 3 0 12 0>,<0 0 0 0 0 0>,<0 0 0 0 0 0>,<0 0 0 0 0 0>,<0 0 0 0 0 0>,<0 0 0 0 0 0>,<0 0 0 0 0 0>,<0 0 0 0 0 0>,<0 0 0 0 0 0>,<0 0 0 0 0 0>,<0 0 0 0 0 0>,<0 0 0 0 0 0>,<0 0 0 0 0 0>,<0 0 0 0 0 0>,<0 0 0 0 0 0>,<0 0 0 0 0 0>
LogRHI: Display: Encountered a new graphics PSO: 898510125
LogRHI: Display: New Graphics PSO (898510125) Description: 432A3E5557E9ED8328AC7E6CDA5AA6FC2F0B2439,0FED48EC019CFDD251488DE33D563DCFFD7E69DA,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,<0 1 0 0 1 0 7 0 1 0 0 1 0 0 0 1 0 0 1 0 0 0 1 0 0 1 0 0 0 1 0 0 1 0 15 0 1 0 0 1 0 15 0 1 0 0 1 0 15 0 1 0 0 1 0 15 0>,<0.000000 0.000000 2 0 0 0>,<0 7 0 7 0 0 0 0 7 0 0 0 255 255>,1,11,1051148,2,2,0,0,0,4,10,1114633,0,0,18,2569,0,0,37,1051145,0,0,37,2585,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15360,15391,1055,1027,1025,0,0,0,2,2,<0 0 4 0 32 0>,<0 16 2 1 32 0>,<0 0 0 0 0 0>,<0 0 0 0 0 0>,<0 0 0 0 0 0>,<0 0 0 0 0 0>,<0 0 0 0 0 0>,<0 0 0 0 0 0>,<0 0 0 0 0 0>,<0 0 0 0 0 0>,<0 0 0 0 0 0>,<0 0 0 0 0 0>,<0 0 0 0 0 0>,<0 0 0 0 0 0>,<0 0 0 0 0 0>,<0 0 0 0 0 0>

并会在Saved/CoolectedPSOs中创建以下文件:

注意,默认只会采集当前的MaterialQualityLevel,如果想要采集其他质量的,需要切换后重新跑。

生成*.stablepc.csv

使用以下commandlet:

1
2
3
4
5
6
Engine/Binaries/Win64/UE4Editor-Cmd.exe
D:/Client/Client.uproject
-run=ShaderPipelineCacheTools expand
D:/PSOCache/*.rec.upipelinecache
D:/PSOCache/*.scl.csv
D:/PSOCache/Client_GLSL_ES3_1_ANDROID.stablepc.csv

以上命令会在引擎的Binaries/Win64下生成Client_GLSL_ES3_1_ANDROID.stablepc.csv文件,注意一定要匹配{PROJECTNAME}_{SHADER_FORMART_NAME}.stablepc.csv这个命名规则。

Android的命名为:Client_GLSL_ES3_1_ANDROID.stablepc.csv
IOS的命名为:Client_SF_METAL.stablepc.csv

生成时具有以下Log:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
D:\PSOCache>"C:\Program Files\Epic Games\UE_4.25\Engine\Binaries\Win64\UE4Editor-Cmd.exe" "D:\PSOExample\PSOExample.uproject" -run=ShaderPipelineCacheTools expand D:/PSOCache/*.rec.upipelinecache D:/PSOCache/*.scl.csv D:/PSOCache/PSOExample_GLSL_ES3_1_ANDROID.stablepc.csv
[2021.04.22-08.57.39:623][ 0]LogTargetPlatformManager: Display: Building Assets For Windows
[2021.04.22-08.57.39:648][ 0]LogAudioDebug: Display: Lib vorbis DLL was dynamically loaded.
[2021.04.22-08.57.39:841][ 0]LogShaderCompilers: Display: Using Local Shader Compiler.
[2021.04.22-08.57.40:492][ 0]LogDerivedDataCache: Display: Max Cache Size: 512 MB
[2021.04.22-08.57.40:523][ 0]LogDerivedDataCache: Display: Loaded Boot cache: C:/Users/lipengzha/AppData/Local/UnrealEngine/4.25/DerivedDataCache/Boot.ddc
[2021.04.22-08.57.40:533][ 0]LogDerivedDataCache: Display: Pak cache opened for reading ../../../Engine/DerivedDataCache/Compressed.ddp.
[2021.04.22-08.57.44:616][ 0]LogAudioCaptureCore: Display: No Audio Capture implementations found. Audio input will be silent.
[2021.04.22-08.57.44:616][ 0]LogAudioCaptureCore: Display: No Audio Capture implementations found. Audio input will be silent.
[2021.04.22-08.57.45:274][ 0]LogShaderPipelineCacheTools: Display: Loading D:/PSOCache/ShaderStableInfo-Global-GLSL_ES3_1_ANDROID.scl.csv...
[2021.04.22-08.57.45:275][ 0]LogShaderPipelineCacheTools: Display: Loading D:/PSOCache/ShaderStableInfo-PSOExample-GLSL_ES3_1_ANDROID.scl.csv...
[2021.04.22-08.57.45:280][ 0]LogShaderPipelineCacheTools: Display: Loaded 548 shader info lines from D:/PSOCache/ShaderStableInfo-Global-GLSL_ES3_1_ANDROID.scl.csv.
[2021.04.22-08.57.45:287][ 0]LogShaderPipelineCacheTools: Display: Loaded 5707 shader info lines from D:/PSOCache/ShaderStableInfo-PSOExample-GLSL_ES3_1_ANDROID.scl.csv.
[2021.04.22-08.57.45:287][ 0]LogShaderPipelineCacheTools: Display: Loaded 6255 unique shader info lines total.
[2021.04.22-08.57.45:289][ 0]LogShaderPipelineCacheTools: Display: Loading D:/PSOCache/++UE4+Release-4.25-CL-13942748-PSOExample_GLSL_ES3_1_ANDROID_000843BC08D905AF09E24614C6A6086F.rec.upipelinecache....
[2021.04.22-08.57.45:293][ 0]LogShaderPipelineCacheTools: Display: Loaded 105 PSOs
[2021.04.22-08.57.45:297][ 0]LogShaderPipelineCacheTools: Display: Loaded 105 PSOs total [Usage Mask Merged = 0].
[2021.04.22-08.57.45:325][ 0]LogShaderPipelineCacheTools: Display: Generated 478 stable PSOs total
[2021.04.22-08.57.45:344][ 0]LogShaderPipelineCacheTools: Display: Wrote stable PSOs, 479 lines (541.8 KB) to D:/PSOCache/PSOExample_GLSL_ES3_1_ANDROID.stablepc.csv
[2021.04.22-08.57.45:346][ 0]LogInit: Display:
[2021.04.22-08.57.45:349][ 0]LogInit: Display: Success - 0 error(s), 0 warning(s)
[2021.04.22-08.57.45:353][ 0]LogInit: Display:
Execution of commandlet took: 0.08 seconds
[2021.04.22-08.57.45:408][ 0]LogShaderCompilers: Display: Shaders left to compile 0
[2021.04.22-08.57.45:621][ 0]LogContentStreaming: Display: There are 1 unreleased StreamingManagers

最终PSO所需要的所有文件:

1
2
3
4
5
6
7
8
D:\PSOCache>tree /a /f
卷 Data 的文件夹 PATH 列表
卷序列号为 004B-E876
D:.
++UE4+Release-4.25-CL-13942748-PSOExample_GLSL_ES3_1_ANDROID_000843BC08D905AF09E24614C6A6086F.rec.upipelinecache
PSOExample_GLSL_ES3_1_ANDROID.stablepc.csv
ShaderStableInfo-Global-GLSL_ES3_1_ANDROID.scl.csv
ShaderStableInfo-PSOExample-GLSL_ES3_1_ANDROID.scl.csv

我把测试工程生成的文件备份了一份:PSOCache.7z,可以查看每个文件中的内容。

生成*.stable.upipelinecache

把生成的*stablepc.csv放到Build/Android/PipelineCaches目录下,注意Build/PLATFORM这个Platform是编译平台,不是Cook的资源平台,Android的包就是Android而不是Android_ASTC等。
之后重新打包即可。

引擎在Cook时通过stavlepc.csv创建PipelineCache的代码:

Editor\UnrealEd\Private\CookOnTheFlyServer.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
void UCookOnTheFlyServer::CreatePipelineCache(const ITargetPlatform* TargetPlatform, const FString& LibraryName)
{
// make sure we have a registry generated for all the platforms
const FString TargetPlatformName = TargetPlatform->PlatformName();
TArray<FString>* SCLCSVPaths = OutSCLCSVPaths.Find(FName(TargetPlatformName));
if (SCLCSVPaths && SCLCSVPaths->Num())
{
TArray<FName> ShaderFormats;
TargetPlatform->GetAllTargetedShaderFormats(ShaderFormats);
for (FName ShaderFormat : ShaderFormats)
{
// *stablepc.csv or *stablepc.csv.compressed
const FString Filename = FString::Printf(TEXT("*%s_%s.stablepc.csv"), *LibraryName, *ShaderFormat.ToString());
const FString StablePCPath = FPaths::ProjectDir() / TEXT("Build") / TargetPlatform->IniPlatformName() / TEXT("PipelineCaches") / Filename;
const FString StablePCPathCompressed = StablePCPath + TEXT(".compressed");

TArray<FString> ExpandedFiles;
IFileManager::Get().FindFilesRecursive(ExpandedFiles, *FPaths::GetPath(StablePCPath), *FPaths::GetCleanFilename(StablePCPath), true, false, false);
IFileManager::Get().FindFilesRecursive(ExpandedFiles, *FPaths::GetPath(StablePCPathCompressed), *FPaths::GetCleanFilename(StablePCPathCompressed), true, false, false);
if (!ExpandedFiles.Num())
{
UE_LOG(LogCook, Display, TEXT("---- NOT Running UShaderPipelineCacheToolsCommandlet for platform %s shader format %s, no files found at %s"), *TargetPlatformName, *ShaderFormat.ToString(), *StablePCPath);
}
else
{
UE_LOG(LogCook, Display, TEXT("---- Running UShaderPipelineCacheToolsCommandlet for platform %s shader format %s"), *TargetPlatformName, *ShaderFormat.ToString());

const FString OutFilename = FString::Printf(TEXT("%s_%s.stable.upipelinecache"), *LibraryName, *ShaderFormat.ToString());
const FString PCUncookedPath = FPaths::ProjectDir() / TEXT("Content") / TEXT("PipelineCaches") / TargetPlatform->IniPlatformName() / OutFilename;

if (IFileManager::Get().FileExists(*PCUncookedPath))
{
UE_LOG(LogCook, Warning, TEXT("Deleting %s, cooked data doesn't belong here."), *PCUncookedPath);
IFileManager::Get().Delete(*PCUncookedPath, false, true);
}

const FString PCCookedPath = ConvertToFullSandboxPath(*PCUncookedPath, true);
const FString PCPath = PCCookedPath.Replace(TEXT("[Platform]"), *TargetPlatformName);


FString Args(TEXT("build "));
Args += TEXT("\"");
Args += StablePCPath;
Args += TEXT("\"");

int32 NumMatched = 0;
for (int32 Index = 0; Index < SCLCSVPaths->Num(); Index++)
{
if (!(*SCLCSVPaths)[Index].Contains(ShaderFormat.ToString()))
{
continue;
}
NumMatched++;
Args += TEXT(" ");
Args += TEXT("\"");
Args += (*SCLCSVPaths)[Index];
Args += TEXT("\"");
}
if (!NumMatched)
{
UE_LOG(LogCook, Warning, TEXT("Shader format %s for platform %s had this file %s, but no .scl.csv files."), *ShaderFormat.ToString(), *TargetPlatformName, *StablePCPath);
for (int32 Index = 0; Index < SCLCSVPaths->Num(); Index++)
{
UE_LOG(LogCook, Warning, TEXT(" .scl.csv file: %s"), *((*SCLCSVPaths)[Index]));
}
continue;
}

Args += TEXT(" ");
Args += TEXT("\"");
Args += PCPath;
Args += TEXT("\"");
UE_LOG(LogCook, Display, TEXT(" With Args: %s"), *Args);

int32 Result = UShaderPipelineCacheToolsCommandlet::StaticMain(Args);

if (Result)
{
LogCookerMessage(FString::Printf(TEXT("UShaderPipelineCacheToolsCommandlet failed %d"), Result), EMessageSeverity::Error);
}
else
{
UE_LOG(LogCook, Display, TEXT("---- Done running UShaderPipelineCacheToolsCommandlet for platform %s"), *TargetPlatformName);
}
}
}
}
}

实际使用stablepc.csv的地方就是用它来执行ShaderPipelineCacheTools这个commandlet生成upipelinecache文件并打至包内。

ShaderPipelineCacheToolsCommandlet的执行命令为:

1
2
3
4
5
6
7
Engine\Binaries\Win64\UE4Editor-Cmd.exe
D:\PSOExample\PSOExample.uproject
-run=ShaderPipelineCacheTools build
"D:\PSOExample/Build/Android/PipelineCaches/*PSOExample_GLSL_ES3_1_ANDROID.stablepc.csv"
"D:\PSOExample/Saved/Cooked/Android_ASTC/PSOExample/Metadata/PipelineCaches/ShaderStableInfo-Global-GLSL_ES3_1_ANDROID.scl.csv"
"D:\PSOExample/Saved/Cooked/Android_ASTC/PSOExample/Metadata/PipelineCaches/ShaderStableInfo-PSOExample-GLSL_ES3_1_ANDROID.scl.csv"
"D:\PSOExample/Saved/Cooked/Android_ASTC/PSOExample/Content/PipelineCaches/Android/PSOExample_GLSL_ES3_1_ANDROID.stable.upipelinecache"

生成*.stable.upipelinecache文件的包内路径为Content\PipelineCaches\Android

1
2
3
4
5
6
D:\PSOExample\Saved\Cooked\Android_ASTC\PSOExample\Content\PipelineCaches>tree /a /f
卷 Windows 的文件夹 PATH 列表
卷序列号为 0C49-9EA3
C:.
\---Android
PSOExample_GLSL_ES3_1_ANDROID.stable.upipelinecache

因为它是位于Content下并会打包进pak的文件,我们也可以对其进行热更。

当安装了包含upipelinecache的包,在运行时就会有以下log:

1
2
3
4
5
6
7
8
9
10
11
LogShaderLibrary: Display: Using ../../../PSOExample/Content/ShaderArchive-PSOExample-GLSL_ES3_1_ANDROID.ushaderbytecode for material shader code. Total 3053 unique shaders.
LogShaderLibrary: Display: Cooked Context: Using Shared Shader Library PSOExample
LogRHI: Display: Opened pipeline cache after state change and enqueued 0 of 0 tasks for precompile.
LogRHI: Base name for record PSOs is ../../../PSOExample/Saved/CollectedPSOs/++UE4+Release-4.25-CL-13942748-PSOExample_GLSL_ES3_1_ANDROID_00087B4B08D905BBC5A827F40CA03A0C.rec.upipelinecache
LogRHI: FPipelineCacheFile Header Game Version: 13942748
LogRHI: FPipelineCacheFile Header Engine Data Version: 17
LogRHI: FPipelineCacheFile Header TOC Offset: 38155
LogRHI: FPipelineCacheFile File Size: 51011 Bytes
LogRHI: Opened FPipelineCacheFile: ../../../PSOExample/Content/PipelineCaches/Android/PSOExample_GLSL_ES3_1_ANDROID.stable.upipelinecache (GUID: 00000000000000000000000000000000) with 102 entries.
LogRHI: Scanning Binary program cache, using Shader Pipeline Cache version 6988202F47BA858F3F0DE483D7DB0606
LogRHI: AndroidEGL:SwapBuffers eglGetCompositorTimingANDROID EGL_COMPOSITE_DEADLINE_ANDROID=2718926192606265, EGL_COMPOSITE_INTERVAL_ANDROID=16559027, EGL_COMPOSITE_TO_PRESENT_LATENCY_ANDROID=14559027

PSO Cache的加载与热更

ShaderCode类似,引擎在启动时也是会自动加载PSO Cache的,在FEngineLoop中通过调用FPipelineCacheFile::OpenPipelineFileCache读取*.stable.upipelinecache的。

PreInitPreStartupScreen中加载PSO的代码:

Runtime/Launch/Private/LaunchEngineLoop.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
int32 FEngineLoop::PreInitPreStartupScreen(const TCHAR* CmdLine)
{
// ...
{
bool bUseCodeLibrary = FPlatformProperties::RequiresCookedData() || GAllowCookedDataInEditorBuilds;
if (bUseCodeLibrary)
{
{
SCOPED_BOOT_TIMING("FShaderCodeLibrary::InitForRuntime");
// Will open material shader code storage if project was packaged with it
// This only opens the Global shader library, which is always in the content dir.
FShaderCodeLibrary::InitForRuntime(GMaxRHIShaderPlatform);
}

#if !UE_EDITOR
// Cooked data only - but also requires the code library - game only
if (FPlatformProperties::RequiresCookedData())
{
SCOPED_BOOT_TIMING("FShaderPipelineCache::Initialize");
// Initialize the pipeline cache system. Opening is deferred until the manual call to
// OpenPipelineFileCache below, after content pak's ShaderCodeLibraries are loaded.
FShaderPipelineCache::Initialize(GMaxRHIShaderPlatform);
}
#endif //!UE_EDITOR
}
}
// ...
//Handle opening shader library after our EarlyLoadScreen
{
LLM_SCOPE(ELLMTag::Shaders);
SCOPED_BOOT_TIMING("FShaderCodeLibrary::OpenLibrary");

// Open the game library which contains the material shaders.
FShaderCodeLibrary::OpenLibrary(FApp::GetProjectName(), FPaths::ProjectContentDir());
for (const FString& RootDir : FPlatformMisc::GetAdditionalRootDirectories())
{
FShaderCodeLibrary::OpenLibrary(FApp::GetProjectName(), FPaths::Combine(RootDir, FApp::GetProjectName(), TEXT("Content")));
}

// Now our shader code main library is opened, kick off the precompile, if already initialized
FShaderPipelineCache::OpenPipelineFileCache(GMaxRHIShaderPlatform);
}
// ...
}

FShaderPipelineCache::OpenPipelineFileCache有两个重载版本:

Runtime\RenderCore\Private\ShaderPipelineCache.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
bool FShaderPipelineCache::OpenPipelineFileCache(EShaderPlatform Platform)
{
bool bFileOpen = false;
if (GConfig)
{
FString LastOpenedName;
if ((GConfig->GetString(FShaderPipelineCacheConstants::SectionHeading, FShaderPipelineCacheConstants::LastOpenedKey, LastOpenedName, *GGameUserSettingsIni) || GConfig->GetString(FShaderPipelineCacheConstants::SectionHeading, FShaderPipelineCacheConstants::LastOpenedKey, LastOpenedName, *GGameIni)) && LastOpenedName.Len())
{
bFileOpen = OpenPipelineFileCache(LastOpenedName, Platform);
}
}

if (!bFileOpen)
{
bFileOpen = OpenPipelineFileCache(FApp::GetProjectName(), Platform);
}

return bFileOpen;
}

bool FShaderPipelineCache::OpenPipelineFileCache(FString const& Name, EShaderPlatform Platform)
{
if (ShaderPipelineCache)
return ShaderPipelineCache->Open(Name, Platform);
else
return false;
}

引擎启动的时候默认读取的就是OpenPipelineFileCache(FApp::GetProjectName(), Platform),也就是PSOExample_GLSL_ES3_1_ANDROID.stable.upipelinecache,Platform参数可以通过传递全局对象GMaxRHIShaderPlatform来获取当前运行的平台。

UE也提供了一个console命令可以指定加载stable.upipelinecacher.ShaderPipelineCache.Open,还有几个其他控制PSO的console命令:

Runtime/RenderCore/Private/ShaderPipelineCache.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static FAutoConsoleCommand LoadPipelineCacheCmd(
TEXT("r.ShaderPipelineCache.Open"),
TEXT("Takes the desired filename to open and then loads the pipeline file cache."),
FConsoleCommandWithArgsDelegate::CreateStatic(ConsoleCommandLoadPipelineFileCache)
);

static FAutoConsoleCommand SavePipelineCacheCmd(
TEXT("r.ShaderPipelineCache.Save"),
TEXT("Save the current pipeline file cache."),
FConsoleCommandDelegate::CreateStatic(ConsoleCommandSavePipelineFileCache)
);

static FAutoConsoleCommand ClosePipelineCacheCmd(
TEXT("r.ShaderPipelineCache.Close"),
TEXT("Close the current pipeline file cache."),
FConsoleCommandDelegate::CreateStatic(ConsoleCommandClosePipelineFileCache)
);

static FAutoConsoleCommand SwitchModePipelineCacheCmd(
TEXT("r.ShaderPipelineCache.SetBatchMode"),
TEXT("Sets the compilation batch mode, which should be one of:\n\tPause: Suspend precompilation.\n\tBackground: Low priority precompilation.\n\tFast: High priority precompilation."),
FConsoleCommandWithArgsDelegate::CreateStatic(ConsoleCommandSwitchModePipelineCacheCmd)
);

这样只需要在热更包中包含最新的*.stable.upipelinecache,之后调用OpenPipelineFileCache加载最新的PSO Cache即可,可以与ShaderCode的热更流程保持一致。

生成新的PSO Cache需要关键的两种数据:

  1. 运行时捕获的PSO数据(upipelinecache)
  2. ShaderStableInfo(位于Metadata目录下)

因为ShaderCode是可以热更的,而ShaderStableInfo可以通过Cook最新的工程获得,所以PSO Cache也是可以通过热更Shader并不断地捕获最新的PSO数据进行迭代更新的。
准备有时间给HotPacther中增加PSO Cache的热更功能,这样也可以把PSO Cache的部署和打包集成至自动化地热更流程,先挖个坑。

因为当开启了r.ShaderPipelineCache.Enabled=1,在引擎启动时就会自动加载项目的PSO Cache,而引擎中做了限制,只能够加载一次,后续调用OpenPipelineFileCache的都不会被加载:

Runtime\RHI\Private\PipelineFileCache.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
bool FPipelineFileCache::OpenPipelineFileCache(FString const& Name, EShaderPlatform Platform, FGuid& OutGameFileGuid)
{
bool bOk = false;
OutGameFileGuid = FGuid();

if(IsPipelineFileCacheEnabled())
{
FRWScopeLock Lock(FileCacheLock, SLT_Write);

if(FileCache == nullptr)
{
FileCache = new FPipelineCacheFile();

bOk = FileCache->OpenPipelineFileCache(Name, Platform, OutGameFileGuid);

// File Cache now exists - these caches should be empty for this file otherwise will have false positives from any previous file caching - if not something has been caching when it should not be
check(NewPSOs.Num() == 0);
check(NewPSOHashes.Num() == 0);
check(RunTimeToPSOUsage.Num() == 0);
}
}

return bOk;
}

当引擎默认加载执行之后FileCache就不为nullptr了,后续所有的加载调用都会直接返回false,解决办法就是,让引擎启动时不自动加载PSO Cache,等到运行时热更之后由我们手动加载,翻了下代码,可以从这个IsPipelineFileCacheEnabled检测中做:

Runtime\RHI\Private\PipelineFileCache.cpp
1
2
3
4
5
6
7
8
9
10
11
12
bool FPipelineFileCache::IsPipelineFileCacheEnabled()
{
static bool bOnce = false;
static bool bCmdLineForce = false;
if (!bOnce)
{
bOnce = true;
bCmdLineForce = FParse::Param(FCommandLine::Get(), TEXT("psocache"));
UE_CLOG(bCmdLineForce, LogRHI, Warning, TEXT("****************************** Forcing PSO cache from command line"));
}
return FileCacheEnabled && (bCmdLineForce || CVarPSOFileCacheEnabled.GetValueOnAnyThread() == 1);
}

它的返回值依赖了两个值:FileCacheEnabled以及CVarPSOFileCacheEnabled

FileCacheEnabledFPipelineFileCache::Initialize中被赋值,IOS之外的平台总是true,IOS则依赖于FPipelineFileCache::ShouldEnableFileCache的结果。

CVarPSOFileCacheEnabled是一个控制台变量,用来控制r.ShaderPipelineCache.Enabled的值:

Runtime\RHI\Private\PipelineFileCache.cpp
1
2
3
4
5
6
static TAutoConsoleVariable<int32> CVarPSOFileCacheEnabled(
TEXT("r.ShaderPipelineCache.Enabled"),
PIPELINE_CACHE_DEFAULT_ENABLED,
TEXT("1 Enables the PipelineFileCache, 0 disables it."),
ECVF_Default | ECVF_RenderThreadSafe
);

我们需要做的有三步:

  1. 引擎默认启动时CVarPSOFileCacheEnabled的值为false
  2. 运行时手动修改CVarPSOFileCacheEnabled值,开启PSO Cache
  3. 加载PSO Cache

具体实现流程:

  1. DefaultEngine.ini中将r.ShaderPipelineCache.Enabled=0并打包
DefaultEngine.ini
1
2
[ConsoleVariables]
r.ShaderPipelineCache.Enabled=0

然后写两个函数在运行时开启和加载PSO:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include "ShaderPipelineCache.h"
#include "RHIShaderFormatDefinitions.inl"
#include "HAL/IConsoleManager.h"

bool UFlibShaderPipelineCacheHelper::EnableShaderPipelineCache(bool bEnable)
{
UE_LOG(LogHotPatcher,Display,TEXT("EnableShaderPipelineCache %s"),bEnable?TEXT("true"):TEXT("false"));
auto Var = IConsoleManager::Get().FindConsoleVariable(TEXT("r.ShaderPipelineCache.Enabled"));
if(Var)
{
Var->Set( bEnable ? 1 : 0);
}
return !!Var;
}

bool UFlibShaderPipelineCacheHelper::LoadShaderPipelineCache(const FString& Name)
{
UE_LOG(LogHotPatcher,Display,TEXT("Load Shader pipeline cache %s for platform %d"),*Name,*ShaderPlatformToShaderFormatName(GMaxRHIShaderPlatform).ToString());
return FShaderPipelineCache::OpenPipelineFileCache(Name,GMaxRHIShaderPlatform);
}

这样即可实现PSO Cache的延迟加载,手动加载时机在热更之后加载即可。

延迟采集和存储

因为采集和存储PSO Cache具有额外的性能消耗,所以可以把采集和存储PSO数据关闭,根据需求在运行时再开启。
DefaultEngine.ini中关闭LogPSOSaveBoundPSOLog,打基础包时就不会自动采集和自动存储了:

DefaultEngine.ini
1
2
3
[ConsoleVariables]
r.ShaderPipelineCache.LogPSO=0
r.ShaderPipelineCache.SaveBoundPSOLog=0

然后在运行时开启:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
UENUM(BlueprintType)
enum class EPSOSaveMode : uint8
{
Incremental = 0, // Fast(er) approach which saves new entries incrementally at the end of the file, replacing the table-of-contents, but leaves everything else alone.
BoundPSOsOnly = 1, // Slower approach which consolidates and saves all PSOs used in this run of the program, removing any entry that wasn't seen, and sorted by the desired sort-mode.
SortedBoundPSOs = 2 // Slow save consolidates all PSOs used on this device that were never part of a cache file delivered in game-content, sorts entries into the desired order and will thus read-back from disk.
};

bool UFlibShaderPipelineCacheHelper::SavePipelineFileCache(EPSOSaveMode Mode)
{
return FShaderPipelineCache::SavePipelineFileCache((FPipelineFileCache::SaveMode)Mode);
}

bool UFlibShaderPipelineCacheHelper::EnableLogPSO(bool bEnable)
{
UE_LOG(LogHotPatcher,Display,TEXT("EnableLogPSO %s"),bEnable?TEXT("true"):TEXT("false"));
auto Var = IConsoleManager::Get().FindConsoleVariable(TEXT("r.ShaderPipelineCache.LogPSO"));
if(Var)
{
Var->Set( bEnable ? 1 : 0);
}
return !!Var;
}

bool UFlibShaderPipelineCacheHelper::EnableSaveBoundPSOLog(bool bEnable)
{
UE_LOG(LogHotPatcher,Display,TEXT("EnableSaveBoundPSOLog %s"),bEnable?TEXT("true"):TEXT("false"));
auto Var = IConsoleManager::Get().FindConsoleVariable(TEXT("r.ShaderPipelineCache.SaveBoundPSOLog"));
if(Var)
{
Var->Set( bEnable ? 1 : 0);
}
return !!Var;
}

开启SaveBoundPSOLog后会自动存储采集的PSO数据,可以不开启自动存储,在运行时通过调用FShaderPipelineCache::SavePipelineFileCache手动存储。

错误处理

Cook没有生成.scl.csv

注意,一定要为项目开启ShaderStableKeys,不然不会生成.scl.csv文件。

运行时没有生成upipelinecache文件

请严格按照运行时捕获PSO数据中的步骤执行。

  1. 确认是否开启r.ShaderPipelineCache.Enabled(DefaultEngine.ini或DeviceProfile)。
  2. 在ue4commandline.txt中添加-logPSO参数。

Bad PSO

如果使用公版引擎,上述流程就是完整的流程,但是有时项目需要修改引擎支持一些渲染特性,如添加Multi-subpasshint支持:

Runtime\RHI\Public\PipelineFileCache.h
1
2
3
4
5
6
7
8
9
10
11
12
struct RHI_API FPipelineCacheFileFormatPSO
{
// ...
struct RHI_API GraphicsDescriptor
{
// uint8 SubpassHint; to SubpassHint[8];
uint8 SubpassHint[8];
uint8 SubpassIndex;
// ...
};
// ...
};

这处变动需要同时修改GraphicsDescriptor::StateToString()GraphicsDescriptor::StateFromString()两个函数,加入multi-subpasshint的序列化支持。

但是,修改之后使用-run=ShaderPipelineCacheTools生成*.stablepc.csv时有以下报错的Log:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
LogShaderPipelineCacheTools: Expanding matched    1 files: D:\PipelineCaches\*.rec.upipelinecache
LogShaderPipelineCacheTools: : D:\PipelineCaches\++UE4+Release-4.25-CL-0-PSOCaching_GLSL_ES3_1_ANDROID_0008BF4D08D9069169A7B6820601243D.rec.upipelinecache
LogShaderPipelineCacheTools: Expanding matched 2 files: D:\PipelineCaches\*.scl.csv
LogShaderPipelineCacheTools: : D:\PipelineCaches\ShaderStableInfo-Global-GLSL_ES3_1_ANDROID.scl.csv
LogShaderPipelineCacheTools: : D:\PipelineCaches\ShaderStableInfo-PSOCaching-GLSL_ES3_1_ANDROID.scl.csv
LogShaderPipelineCacheTools: Display: Loading D:\PipelineCaches\ShaderStableInfo-Global-GLSL_ES3_1_ANDROID.scl.csv...
LogShaderPipelineCacheTools: Display: Loading D:\PipelineCaches\ShaderStableInfo-PSOCaching-GLSL_ES3_1_ANDROID.scl.csv...
LogShaderPipelineCacheTools: Display: Loaded 926 shader info lines from D:\PipelineCaches\ShaderStableInfo-Global-GLSL_ES3_1_ANDROID.scl.csv.
LogShaderPipelineCacheTools: Display: Loaded 9266 shader info lines from D:\PipelineCaches\ShaderStableInfo-PSOCaching-GLSL_ES3_1_ANDROID.scl.csv.
LogShaderPipelineCacheTools: Display: Loaded 10192 unique shader info lines total.
LogShaderPipelineCacheTools: Display: Loading D:\PipelineCaches\++UE4+Release-4.25-CL-0-PSOCaching_GLSL_ES3_1_ANDROID_0008BF4D08D9069169A7B6820601243D.rec.upipelinecache....
LogShaderPipelineCacheTools: Display: Loaded 115 PSOs
LogShaderPipelineCacheTools: Warning: Bad PSO found discarding [Invertibility=FAIL Verify=PASS in: D:\PipelineCaches\++UE4+Release-4.25-CL-0-PSOCaching_GLSL_ES3_1_ANDROID_0008BF4D08D9069169A7B6820601243D.rec.upipelinecache]
LogShaderPipelineCacheTools: Warning: Bad PSO found discarding [Invertibility=FAIL Verify=PASS in: D:\PipelineCaches\++UE4+Release-4.25-CL-0-PSOCaching_GLSL_ES3_1_ANDROID_0008BF4D08D9069169A7B6820601243D.rec.upipelinecache]
LogShaderPipelineCacheTools: Warning: Bad PSO found discarding [Invertibility=FAIL Verify=PASS in: D:\PipelineCaches\++UE4+Release-4.25-CL-0-PSOCaching_GLSL_ES3_1_ANDROID_0008BF4D08D9069169A7B6820601243D.rec.upipelinecache]
LogShaderPipelineCacheTools: Warning: Bad PSO found discarding [Invertibility=FAIL Verify=PASS in: D:\PipelineCaches\++UE4+Release-4.25-CL-0-PSOCaching_GLSL_ES3_1_ANDROID_0008BF4D08D9069169A7B6820601243D.rec.upipelinecache]
LogShaderPipelineCacheTools: Warning: Bad PSO found discarding [Invertibility=FAIL Verify=PASS in: D:\PipelineCaches\++UE4+Release-4.25-CL-0-PSOCaching_GLSL_ES3_1_ANDROID_0008BF4D08D9069169A7B6820601243D.rec.upipelinecache]
LogShaderPipelineCacheTools: Warning: Bad PSO found discarding [Invertibility=FAIL Verify=PASS in: D:\PipelineCaches\++UE4+Release-4.25-CL-0-PSOCaching_GLSL_ES3_1_ANDROID_0008BF4D08D9069169A7B6820601243D.rec.upipelinecache]
LogShaderPipelineCacheTools: Warning: Bad PSO found discarding [Invertibility=FAIL Verify=PASS in: D:\PipelineCaches\++UE4+Release-4.25-CL-0-PSOCaching_GLSL_ES3_1_ANDROID_0008BF4D08D9069169A7B6820601243D.rec.upipelinecache]
LogShaderPipelineCacheTools: Warning: Bad PSO found discarding [Invertibility=FAIL Verify=PASS in: D:\PipelineCaches\++UE4+Release-4.25-CL-0-PSOCaching_GLSL_ES3_1_ANDROID_0008BF4D08D9069169A7B6820601243D.rec.upipelinecache]
LogShaderPipelineCacheTools: Warning: Bad PSO found discarding [Invertibility=FAIL Verify=PASS in: D:\PipelineCaches\++UE4+Release-4.25-CL-0-PSOCaching_GLSL_ES3_1_ANDROID_0008BF4D08D9069169A7B6820601243D.rec.upipelinecache]
LogShaderPipelineCacheTools: Warning: Bad PSO found discarding [Invertibility=FAIL Verify=PASS in: D:\PipelineCaches\++UE4+Release-4.25-CL-0-PSOCaching_GLSL_ES3_1_ANDROID_0008BF4D08D9069169A7B6820601243D.rec.upipelinecache]

这是因为PSO数据从String的可逆性验证失败了:

Editor\UnrealEd\Private\Commandlets\ShaderPipelineCacheToolsCommandlet.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
bool CheckPSOStringInveribility(const FPipelineCacheFileFormatPSO& Item)
{
FPipelineCacheFileFormatPSO TempItem(Item);
TempItem.Hash = 0;

FString StringRep;
if (Item.Type == FPipelineCacheFileFormatPSO::DescriptorType::Compute)
{
StringRep = TempItem.ComputeDesc.ToString();
}
else
{
StringRep = TempItem.GraphicsDesc.ToString();
}
FPipelineCacheFileFormatPSO DupItem;
FMemory::Memzero(DupItem.GraphicsDesc);
DupItem.Type = Item.Type;
DupItem.UsageMask = Item.UsageMask;
if (Item.Type == FPipelineCacheFileFormatPSO::DescriptorType::Compute)
{
DupItem.ComputeDesc.FromString(StringRep);
}
else
{
DupItem.GraphicsDesc.FromString(StringRep);
}
UE_LOG(LogShaderPipelineCacheTools, Verbose, TEXT("CheckPSOStringInveribility: %s"), *StringRep);

return (DupItem == TempItem) && (GetTypeHash(DupItem) == GetTypeHash(TempItem));
}

关键部分在于DupItem.GraphicsDesc.FromString(StringRep);这行代码中GraphicsDesc的数据没有恢复成功。

经过调试发现,引擎中具有记录PipelineCacheGraphicsDesc的字符串中可被解析元素的数量,也就是生成的*.stablepc.csv中第二列中数据的数量,公版引擎中默认是63个,使用FPipelineCacheGraphicsDescPartsNum记录:

Runtime\RHI\Private\PipelineFileCache.cpp
1
const int32  FPipelineCacheGraphicsDescPartsNum = 63; // parser will expect this number of parts in a description string

生成的*.stablepc.csv中各项状态和数据如下:

刚好是63个。需要注意的是,在GraphicsDescriptor::StateFromString对数据的数量做了检测,FromString的数据数量要与FPipelineCacheGraphicsDescPartsNum的值一致:

Runtime\RHI\Private\PipelineFileCache.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
bool FPipelineCacheFileFormatPSO::GraphicsDescriptor::StateFromString(const FStringView& Src)
{
constexpr int32 PartCount = FPipelineCacheGraphicsDescPartsNum;

TArray<FStringView, TInlineAllocator<PartCount>> Parts;
UE::String::ParseTokens(Src.TrimStartAndEnd(), TEXT(','), [&Parts](FStringView Part) { Parts.Add(Part); });

// check if we have expected number of parts
if (Parts.Num() != PartCount)
{
// instead of crashing let caller handle this case
return false;
}
// ...
}

因为我们增加了multi-subpasshint支持,把SubpassHintuint8改成了uint8[8],增加了7个数据,所以与之对应的FPipelineCacheGraphicsDescPartsNum也要加7,改为70,上面StateFromString验证才能够通过。

修改之后再通过-run=ShaderPipelineCacheTools生成*.stablepc.csv就没有Bas PSO的错误了。

使用与配置

可以通过FShaderPipelineCache的函数在运行时控制构建PSO数据:

Runtime\RenderCore\Public\ShaderPipelineCache.h
1
2
3
4
5
6
7
8
9
10
/** Pauses precompilation. */
static void PauseBatching();
/** Resumes precompilation batching. */
static void ResumeBatching();
/** Returns the number of pipelines waiting for precompilation. */
static uint32 NumPrecompilesRemaining();
/** Returns the number of pipelines actively being precompiled this frame. */
static uint32 NumPrecompilesActive();
/** Sets the precompilation batching mode. */
static void SetBatchMode(BatchMode Mode);

官方建议的做法是在加载屏幕时等待PSO构建完毕,再把LoadingScreen隐藏:

1
2
3
4
5
6
7
8
if(FShaderPipelineCache::NumPrecompilesRemaining() > 0)
{
if (OutDebugReason != nullptr)
{
*OutDebugReason = FString(TEXT("PC: PSO cache still compiling"));
}
return true;
}

也可以在打开UI、过场动画、暂停菜单时构建,通过以下三个函数组合处理:

1
2
3
4
5
6
7
8
9
10
11
12
// 暂停PSO缓存编译
FShaderPipelineCache::PauseBatching();
// 设置PSO的处理模式
// enum class BatchMode
// {
// Background, // The maximum batch size is defined by r.ShaderPipelineCache.BackgroundBatchSize
// Fast, // The maximum batch size is defined by r.ShaderPipelineCache.BatchSize
// Precompile // The maximum batch size is defined by r.ShaderPipelineCache.PrecompileBatchSize
// };
FShaderPipelineCache::SetBatchMode(FShaderPipelineCache::BatchMode::Background);
// 恢复编译PSO
static void ResumeBatching();

引擎中有多个CVars可用于控制PSO的捕获、加载等等配置,详细介绍详见:UE Console Variables and Command,在其中搜索r.shaderpipelinecache即可看到所有支持的CVars。

也可以使用配置在游戏启动时自动构建,修改DefaultEngine.ini[ConsoleVariables]的配置,它们否时定义在Runtime\RenderCore\Private\ShaderPipelineCache.cpp中的ConsoleVariable,可以根据自己的需要在运行时或配置文件中进行修改:

开启了启动时自动构建PSO数据,会有以下log:

1
2
3
4
5
6
7
LogRHI: Base name for record PSOs is ../../../FGame/Saved/CollectedPSOs/++UE4+Release-4.25-CL-0-FGame_SF_METAL_8F3222B7964FE2A89C849E90E0000736.rec.upipelinecache
LogRHI: FPipelineCacheFile Header Game Version: 0
LogRHI: FPipelineCacheFile Header Engine Data Version: 17
LogRHI: FPipelineCacheFile Header TOC Offset: 293853
LogRHI: FPipelineCacheFile File Size: 380497 Bytes
LogRHI: Opened FPipelineCacheFile: ../../../FGame/Content/PipelineCaches/IOS/FGame_SF_METAL.stable.upipelinecache (GUID: 00000000000000000000000000000000) with 690 entries.
LogRHI: Display: Opened pipeline cache and enqueued 441 of 441 tasks for precompile with BatchSize 50 and BatchTime 10.000000.

也可以在DefaultGameUserSettings.ini中设置PSO的SortOrder

DefaultGameUserSettings.ini
1
2
3
4
5
6
[ShaderPipelineCache.CacheFile]
;default is 0
;Default = 0, // Whatever order they are already in.
;FirstToLatestUsed = 1, // Start with the PSOs with the lowest first-frame used and work toward those with the highest.
;MostToLeastUsed = 2 // Start with the most often used PSOs working toward the least.
SortOrder=1

Profiling

可以使用Unreal Ingishts查看shader link的耗时:

以及通过stat pipelinestatecache,可以查看PSO的加载和生成情况。

注意

ShaderLibrary要求

使用PSO的前提要求是必须要把Shader的序列化方式改成share shader library,如果是inline shader的形式,是无法使用Pre-Compiled的PSO的。运行时采集是可以的,但无法利用到下次打的包里。

scl.csv to shk

4.27将StableExtension从scl.csv改成了shkShaderCodeLibrary.cpp#L65

参考资料

全文完,若有不足之处请评论指正。

微信扫描二维码,关注我的公众号。

本文标题:UE项目优化:PSO Cache
文章作者:查利鹏
发布时间:2021/04/22 19:40
本文字数:6.4k 字
原始链接:https://imzlp.com/posts/24336/
许可协议: CC BY-NC-SA 4.0
文章禁止全文转载,摘要转发请保留原文链接及作者信息,谢谢!
您的捐赠将鼓励我继续创作!