MAKE · WRITE · SHARE

UE4-GPUScene

UE4里GPUScene相关的梳理。

1. 引擎传输Primitive数据到GPU的方式

什么是PrimitiveData?

PrimitiveData是UPrimitiveComponent级别的几何数据,包括LocalToWorldMatrix,WorldPosition,AABB,LightMap等数据。

引擎传输Primitive数据到GPU的方式:PrimitiveData是UPrimitiveComponent级别的几何数据,包括Local

传统方式

每个Primitive一个UniformBuffer,每次DrawCall绑定。

1
2
3
4
5
6
7
8
9
10
11
GameThread

→ FPrimitiveSceneProxy

→ CreatePrimitiveUniformBufferImmediate()

→ RHICreateUniformBuffer

→ Draw 时 SetUniformBuffer

→ GPU

上面是概念性路径:UniformBuffer 的创建、更新和绑定是不同操作,缓存策略可能避免每帧重建。不能仅由 DrawCall 数推断每帧上传字节。

简化成本模型:逐 Draw/Primitive 的数据准备与绑定。真实上传量应通过资源更新路径与抓帧核对。

传统绑定仍可以缓存不变数据。GPUScene 的价值还包括统一索引和减少逐 Draw 状态,而不是证明所有传统静态数据每帧都一定完整重传。

GPUScene的方式

UE4 的 GPUScene 路径将 Primitive 参数集中组织,减少逐 Draw 参数绑定,并为按 PrimitiveId 查询数据提供基础。具体可用平台和更新路径以引擎分支为准。

所有的Primitive数据,打包进一个大的StructuredBuffer,GPU里通过PrimitiveId获取。

然后每一帧不变的静态物体的数据就保持不变,只更新变化的静态物体以及动态物体。

动态物体也分为每个视图(不是全部更新),视图会提前收集到需要更新的动态物体。

在原研究分支中,可用“变化的静态 Primitive + 各 View 收集的动态数据”理解更新来源;不是跨版本固定字节公式。

GPUScene的方式:在原研究分支中,可用“变化的静态 Primitive + 各 View 收集的动态数据”理解更新

2. GPUScene的工作流程

GPUScene是一个大的功能,它是Primitive基础数据的容器,有一些功能比如DistanceField、LightMap、InstanceCulling这些,有自己的更新逻辑。

如距离场数据的更新,以前在这里写过

UE4-HISM剔除更新DFShadow

GPUScene基础的更新流程

标脏 → 收集 Dirty Primitive → 构建 PrimitiveShaderData → 批量写入 StructuredBuffer → Shader 通过 PrimitiveId 读取

更新标记Dirty路径:GameThread——>RenderThread

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
UPrimitiveComponent::MarkRenderTransformDirty()
UPrimitiveComponent::MarkRenderStateDirty()

——>

FScene::UpdatePrimitiveTransform()
FScene::UpdatePrimitiveSceneInfo()

——>

Scene->GPUScene.MarkPrimitiveDirty(PrimitiveSceneInfo);

——>

DirtyPrimitiveSceneInfos.Add(PrimitiveSceneInfo);

Dirty标记完成。不立刻更新,后面统一更新。

GPU更新流程:

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
FSceneRender::Render()
FSceneRenderer::UpdateGPUScene()

——>

统计Dirty数量
NumDirtyPrimitives = GPUScene.DirtyPrimitiveSceneInfos.Num();

——>

为上传准备CPU Staging buffer
TArray<FPrimitiveSceneShaderData> PrimitiveUploadData;

——>

构建每个 Dirty Primitive 的 ShaderData
for (PrimitiveSceneInfo : DirtyList)
{
BuildPrimitiveShaderData(...)
}

——>

写入上传数据

——>

批量更新Buffer到GPU
RHICmdList.UpdateBuffer(
PrimitiveBuffer,
OffsetInBytes,
SizeInBytes,
UploadDataPtr
);


——>

GPU里StructuredBuffer已经更新,shader里获取新的。
PrimitiveSceneData = PrimitiveSceneDataBuffer[PrimitiveId];

多视图共享上传的旧研究

原笔记另记录了对多个阴影视图重复上传动态 Primitive 数据的优化:若同一数据版本已经上传,可以让多个视图引用共享记录,减少重复写入。笔记参考了内部技术文章,不能把参考方案全部视为自研成果。

共享的前提是记录确实与 View 无关。视图相关参数、动态 ID 范围、Buffer 生命周期、并发提交与脏标记都需正确处理。主相机和阴影相机不能仅因指向同一个组件就无条件共享所有数据。

旧记录中的 r.GPUScene.UseViewShareBuffer 属于研究分支开关,不应当作 UE4 官方通用变量。其测试收益依动态对象和视图数量而变;当前没有重新取得原构建和可复现抓帧,因此不补写性能数字。

多 View 情况下观察重复上传工作的抓帧参考

原笔记配图:多 View 情况下观察重复上传工作的抓帧参考。

旧测试场景的第一张统计截图

原笔记配图:旧测试场景的第一张统计截图;条件来自原记录,不作为本次重新测得的结果。

旧测试场景的第二张统计截图

原笔记配图:旧测试场景的第二张统计截图;截图本身不足以独立证明可迁移的性能收益。

总结

GPUScene 改变数据组织与绑定方式,增量更新避免重复工作;多 View 共享则需要进一步证明数据和生命周期相容。示意调用链用于定位方向,函数签名应回到实际源码分支核对。