Highlight
Apple 与 Larian Studios(《神界:原罪2》)和 4A Games(《地铁:离去》)合作,总结出 Apple GPU 上高端游戏的四步优化法:分析瓶颈、优化 shader、减少内存带宽、增加 GPU 工作负载重叠。
核心内容
你把一款高端 PC 游戏移植到 Apple Silicon。画面看起来不错,但帧率不稳定,某些场景突然掉到 20fps。问题出在哪?
Apple GPU 采用 TBDR(Tile-Based Deferred Rendering)架构,和 PC 上的 IMR(Immediate Mode Rendering)完全不同。在 PC 上有效的优化策略,在 Apple GPU 上可能适得其反。
Apple 工程师用一年时间分析了多款游戏,总结出一套系统化的优化方法。
详细内容
四步优化流程
(00:52)
Apple 推荐按这个顺序优化:
- 分析瓶颈 — 用 GPU 工具找出性能限制因素
- 优化 shader — 减少 ALU 和寄存器压力
- 减少内存带宽 — 降低 tile 内存溢出和纹理带宽
- 增加工作负载重叠 — 让 GPU 同时处理多个任务
这个顺序很重要。如果瓶颈在内存带宽,优化 shader 不会有明显效果。
案例分析:《神界:原罪2》
(04:37)
Larian Studios 的《神界:原罪2》在 iPad 上运行时,某些场景性能很差。Apple 用 Xcode 13 的新 GPU Timeline 工具分析后发现:
问题 1:Uber-shader 寄存器压力
游戏用一个巨大的 uber-shader 处理所有材质。这个 shader 包含大量条件分支,但实际每帧只用到其中一小部分。由于 shader 太大,寄存器分配压力高,导致 GPU 无法充分利用并行性。
解决方案:用 function constants 替代运行时条件分支。
// 优化前:运行时条件分支,所有代码都占用寄存器
fragment float4 uberShader(...) {
float4 color;
if (materialType == 0) {
color = computeMetal(...);
} else if (materialType == 1) {
color = computeFabric(...);
} else if (materialType == 2) {
color = computeStone(...);
}
// ... 更多分支
return color;
}
// 优化后:编译期 specialization,只保留用到的代码
[[function_constant(0)]] const bool hasMetal = false;
[[function_constant(1)]] const bool hasFabric = false;
fragment float4 specializedShader(...) {
float4 color;
if (hasMetal) {
color = computeMetal(...);
} else if (hasFabric) {
color = computeFabric(...);
}
return color;
}
(12:25)
关键点:
[[function_constant]]在编译期确定分支- 未使用的代码被编译器剔除
- 寄存器分配更紧凑
- 需要为每种组合创建 pipeline state,但运行时性能更好
问题 2:Tile 内存溢出
TBDR 架构在 on-chip tile 内存中完成渲染。如果每帧的中间数据太多,tile 内存装不下,就会溢出到主内存,性能断崖式下降。
解决方案:减少 render target 的 bit depth,合并 render pass。
// 优化前:多个 render pass,每个都写主内存
Pass 1: G-Buffer (albedo + normal + depth) → 主内存
Pass 2: Lighting → 主内存
Pass 3: Post-processing → 主内存
// 优化后:合并到一个 render pass,数据留在 tile 内存
Pass 1: G-Buffer → Lighting → Post-processing (tile 内存内完成)
(15:24)
关键点:
- TBDR 的优势是 tile 内存带宽远高于主内存
- 每次 render pass 切换都可能导致 tile 数据写回主内存
- 合并 render pass 让中间数据留在 on-chip
- 使用 memoryless attachment 避免写回
问题 3:冗余资源绑定
每帧重复绑定相同的 buffer 和 texture,CPU 开销大。
解决方案:用 argument buffer 缓存绑定状态。
// 优化前:每帧逐个绑定
[encoder setVertexBuffer:buffer1 offset:0 atIndex:0];
[encoder setVertexBuffer:buffer2 offset:0 atIndex:1];
[encoder setFragmentTexture:texture1 atIndex:0];
[encoder setFragmentTexture:texture2 atIndex:1];
// 优化后:用 argument buffer 一次绑定
[encoder setVertexBuffer:argumentBuffer offset:0 atIndex:0];
[encoder useResources:resources usage:MTLResourceUsageRead];
(21:40)
关键点:
- argument buffer 把资源引用打包到一个 buffer 中
- 减少 encoder 的 API 调用次数
- 适合资源数量多的场景
- 配合
useResources:usage:告诉驱动哪些资源会被访问
案例分析:《地铁:离去》
(16:30)
4A Games 的《地铁:离去》在 Apple GPU 上的主要问题是 compute shader 的内存访问模式。
问题:非合并内存访问
compute shader 中线程访问的内存地址分散,导致缓存效率低。
解决方案:重新组织数据布局,让相邻线程访问相邻内存地址。
// 优化前:线程 (x,y) 访问内存位置不连续
uint index = y * width + x;
float value = input[index];
// 优化后:利用 threadgroup memory 合并访问
threadgroup float sharedData[256];
uint localIndex = thread_position_in_threadgroup.x;
sharedData[localIndex] = input[globalIndex];
threadgroup_barrier(mem_flags::mem_threadgroup);
float value = sharedData[localIndex];
(18:45)
关键点:
- threadgroup memory 比 device memory 快得多
threadgroup_barrier确保所有线程完成写入后再读取- 重新组织数据布局让访问模式更规则
- 减少 cache miss 是带宽优化的核心
GPU Timeline 工具
(22:30)
Xcode 13 新增的 GPU Timeline 是分析性能瓶颈的核心工具。它以时间线方式展示:
- 每个 render pass 和 compute dispatch 的 GPU 耗时
- 内存带宽使用情况
- tile 内存压力
- 各阶段的重叠程度
通过时间线可以直观看到:哪个 pass 耗时最长?是否有空闲周期可以重叠更多工作?带宽是否成为瓶颈?
核心启发
-
先用 GPU Timeline 定位瓶颈再优化。不要凭感觉优化,可能优化了半天发现瓶颈在另一个地方。入口 API:Xcode 13 GPU Timeline。
-
用 function constants 替代 uber-shader 的运行时分支。编译期剔除未用代码,大幅降低寄存器压力。入口 API:
[[function_constant(N)]]。 -
合并 render pass,让中间数据留在 tile 内存。TBDR 架构下,一次大 render pass 比多次小 render pass 高效得多。入口 API:
MTLLoadAction+MTLStoreAction+ memoryless attachment。 -
检查 compute shader 的内存访问模式。用 threadgroup memory 缓存频繁访问的数据,确保相邻线程访问相邻地址。入口 API:
threadgroup+threadgroup_barrier。 -
用 argument buffer 减少每帧的资源绑定开销。特别适合资源数量多的场景。入口 API:
MTLArgumentEncoder+useResources:usage:。
关联 Session
- Optimize for variable refresh rate displays — Adaptive-Sync 和 ProMotion 帧率优化
- Enhance your app with Metal ray tracing — Metal 光线追踪 API
- Create image processing apps powered by Apple silicon — Apple Silicon 图像处理优化
- Discover Metal debugging, profiling, and asset creation tools — Xcode 13 Metal 调试和性能分析工具
评论
GitHub Issues · utterances