PSO
PSO的全称是Pipeline State Object,主要是用来给GPU描述如何计算、渲染的设置集合。PSO是伴随新一代的图形API(DX12、Vulkan、Metal)提出的。在这之前的时代,渲染设置是一个状态机管理的,用单个API设置单种渲染状态。现代图形API是把所有的渲染状态打包设置给GPU,这些"捆绑在一起"的渲染状态组合,就是PSO了。Graphics PSO一般包括:
|-----------------------|----------------------------------|
| Graphics PSO | Description |
| Shaders | VS, PS... |
| Vertex Description | Vertex element format, stride... |
| Blend State | Blend operation |
| Rasterizer State | Fill mode, cull mode... |
| Depth Stencil State | Depth test, stencil mask... |
| Render Target setting | RT Format, flags, load, store... |
| MSAA | MSAA Samples |
| Primitive Topology | Triangle Strip, Point... |
| Subpass info | Subpass Num, hints... |
从下面对应代码中,可以看出其中shader是用hash值来代替的,运行时会用这些hash值索引真正的shader二进制:
struct RHI_API GraphicsDescriptor { FSHAHash VertexShader; FSHAHash FragmentShader; FSHAHash GeometryShader; FSHAHash HullShader; FSHAHash DomainShader; ……
PSO Cache
在游戏runtime时创建PSO会非常耗时,测到最高的有100多ms每个PSO。优化方法是使用缓存,某个pso第一次创建好会缓存在内存中,下次再需要用到同样的话可以直接复用。(todo:流程图待补充)
PSO Cache有两个层级:内存中、硬盘本地的二进制文件 Local Binary Cache。讨论时需要根据具体情景区分其指代的含义。
假设构建时没有做处理,游戏app安装后,第一次使用时,会创建PSO,时间开销会很大,如何解决呢?由于游戏用到的PSO是相对固定的,游戏厂商可以在发布游戏之前,通过提前运行内部版本,收集实际使用到的PSO的记录(后文会强调不是PSO本身),运行时先根据此记录预热一遍,等真正渲染需要用到时,即可直接使用内存里的PSO本体了。这也就是 UE4官网文档(链接请点我) 中的图片(下图)所揭示的流程了,注意到其中**"构建包体"的是不断包含"收集上来的PSO记录"的**,即 "构建"→"厂商的内部跑测"→"收集得到PSO的记录"→"再次构建"→......→"最终发布版本"。
(图1)
上图其中的 .spc .shk 是UE5用到的,本文只提UE4传统的PSO。对于UE4而言,如下图所示。这个过程追求高覆盖率 :为了达到最佳效果,你需要确保游戏运行时的PSO收集(即生成 .rec.upipelinecache文件的过程)尽可能覆盖所有的关卡、角色、特效和材质组合 。覆盖率越高,发布后玩家在游戏中遇到的意外卡顿就越少,从而避免在游戏过程中因实时编译PSO导致的帧率骤降和卡顿。

(图2)
两种PSO工作流
官方文档里提到了两种工作方式:
1、Bundled PSO Cache
先说大体印象:对于线性叙事的游戏,更加适合 Bundled PSO Cache ,例如《古墓丽影》《明日之子》这种有剧情的线性关卡的游戏,内容固定、流程线性的游戏(如单人剧情驱动型游戏)。玩家体验路径相对固定,可预测性强。大白话说:**"游戏厂商可以帮你提前玩个遍,采集尽可能完备的PSO。"**侧重点在于跑测完备。
2、PSO Precaching
https://dev.epicgames.com/documentation/en-us/unreal-engine/pso-precaching-for-unreal-engine
先说大体印象:对于内容动态性强、包含大量用户生成内容(UGC)或程序化生成内容的项目,PSO Precache通常是比传统的Bundle PSO Cache更优的选择。例如说《我的世界》。大白话说:"游戏有无限可能,我只能帮你在Runtime时做一下预测,在游戏内尽量提前缓存(precaching的含义)。" 侧重点在于Runtime中对可能需要的PSO的预判,强调联想的策略。
图1和图2、以及下面介绍的Expand PSO 都是 Bundled PSO Cache 工作方式的。
目前的最佳实践是将 Bundle PSO Cache(包含Expand PSO流程) 与 PSO Precache 结合使用,以弥补自动收集可能遗漏的部分,追求接近100%的覆盖率。
知识巩固 >
问题:我能否这么理解~ precache是每一台玩家设备自行预测、缓存。
bundle pso cache由游戏开发商预先跑测、收集pso缓存、再次打进包体里、随包体分发给每个玩家用。
回答:理解非常准确,核心区别确实在于 PSO缓存的收集者和执行者不同。下面是比对表格
| 特性 | PSO Precache (预缓存) | Bundle PSO Cache (打包PSO缓存) |
|---|---|---|
| 发起者 | 玩家设备(运行时自动预测) | 游戏开发商(开发阶段手动收集) |
| 工作时机 | 关卡加载时或对象流送时 | 游戏首次启动时 |
| 核心逻辑 | 分析加载的网格和材质,预测可能需要的PSO并异步编译 | 将开发商测试时记录的实际用到的PSO列表提前编译 |
| 优点 | 自动化,适应动态内容和更新 | 若收集全面,覆盖率极高,能最大程度减少运行时编译 |
| 缺点 | 预测可能不准,会编译一些用不到的PSO;对运行时动态生成的内容可能覆盖不全 | 收集过程繁琐,需大量测试;游戏内容更新后需重新收集 |
在实际项目中,这两种方案并非二选一,而是互补关系。当前的主流最佳实践是将两者结合使用。
-
以PSO Precache为基础:将其作为默认的首选方案。它开箱即用,能解决绝大部分卡顿问题,尤其适合内容动态性较强的项目。
-
用Bundle PSO Cache查漏补缺:PSO Precache可能存在盲区,例如:
● 运行时通过游戏逻辑动态生成的Actor及其材质。● 某些特殊的全局着色器。 ● 在运行时应用的动态材质。
对于这些盲区,可以通过Bundle PSO Cache流程进行针对性的补充收集。在项目配置中,可以设置 r.ShaderPipelineCache.ExcludePrecachePSO=1,以避免重复收集已由PSO Precache覆盖的内容。
知识巩固 <
Expand PSO
提问: 什么是"展开PSO"?
回答: 它特指将游戏运行时收集的原始PSO数据,转换为可供项目打包使用的稳定PSO缓存(含 .stablepc.csv文件)的过程。这个步骤是 Bundle PSO Cache 工作流的核心环节之一。
| 环节 | 输入文件 (Input) | 核心操作 (Process) | 输出文件 (Output) | 最终用途 (Purpose) |
|---|---|---|---|---|
| 1. 准备阶段 | 项目材质与Shader | 项目打包(Cook) | .shk或 .scl.csv文件(稳定的ShaderKey) |
记录所有Shader的唯一标识和依赖关系 |
| 2. 数据收集 | 运行游戏 | 在目标设备(如手机)上实际游玩,触发各种渲染组合 | .rec.upipelinecache文件(运行时PSO记录) |
记录实际用到的PSO列表 |
| 3. Expand PSO | .shk文件 + .rec.upipelinecache文件 |
使用 ShaderPipelineCacheTools工具执行 Expand 操作 |
.stablepc.csv文件(稳定的PSO缓存) |
将运行时记录与ShaderKey关联,生成最终的缓存清单 |
| 4. 打包发布 | .stablepc.csv文件 |
将文件放入项目指定目录(如 Build/Android/PipelineCaches/),重新打包 |
包含PSO缓存的游戏包 | 玩家首次运行时预编译PSO,减少卡顿 |
"Expand PSO" 的命令是:
UE4Editor-Cmd.exe YourProject.uproject -run=ShaderPipelineCacheTools expand [收集的.upipelinecache文件] [对应的.scl.csv文件] [输出的.stablepc.csv文件名]
其中三个变量中的前两个是输入,最后一个是输出。例如:
UE4Editor-Cmd.exe MyGame.uproject -run=ShaderPipelineCacheTools expand C:\PSOCaching\*.rec.upipelinecache C:\PSOCaching\*.scl.csv MyGame_GLSL_ES3_1_ANDROID.stablepc.csv
最终回答 "Expand PSO" 是利用 .scl.csv 与 录制的 .rec.upipelinecache,转换为.stablepc.csv 的过程。
.scl.csv
基础概念
1、Shader存放在哪里?
那么现在的问题就是如何收集、获取我们游戏会用到哪些PSO呢?
参考
1、UE4官网介绍