Highlight
Apple 解释了 Tile Based Deferred Rendering、Tile Memory、Memoryless Render Targets、Tile Shaders、Argument Buffers 和 Indirect Command Buffers 如何让 Metal 应用减少带宽、降低内存占用,并把更复杂的渲染循环移到 GPU 侧执行。
核心内容
很多图形程序的性能瓶颈常常来自内存搬运,shader 本身未必是主因。传统渲染管线习惯把 G-Buffer、MSAA 采样结果、临时颜色附件写回外部内存,再在下一轮 pass 里读回来。画面越复杂,带宽越容易成为瓶颈。
Apple GPU 的前提不同。CPU 和 GPU 共享系统内存,GPU 另外有一块片上 Tile Memory,但没有独立显存。Apple 选择的路径是 Tile Based Deferred Rendering(TBDR,基于 tile 的延迟渲染):先把几何分到 tile,再在每个 tile 内完成像素处理。只要把中间结果留在 Tile Memory,很多读写就不需要发生。
这场 session 的价值在于把 Metal 的几个 API 放回硬件背景里看。loadAction、storeAction、memoryless storage、programmable blending、tile shader、imageblock、argument buffer、indirect command buffer 都有硬件背景。它们共同服务一个目标:少访问系统内存,把可并行的决策交给 GPU。
详细内容
TBDR 把渲染拆成 tiling 和 rendering
(01:50)Apple GPU 的一个 render pass 分成两个阶段。Tiling phase 处理整个 pass 的几何,把变换后的 primitive 放入对应 tile。Rendering phase 逐个 tile 执行 load action、光栅化、可见性计算、fragment shading 和 store action。
工作流示例:
- 进入 render pass 时,先判断每个 attachment 是否需要上一轮内容。
- 不需要旧内容的 attachment,用 clear 初始化 Tile Memory。
- pass 结束时,只保存后续还要展示或采样的结果。
- 只服务本 pass 的深度、模板或中间颜色数据,结束后可以丢弃。
关键点:
- load action 的成本来自把 attachment 内容搬入 Tile Memory。
- store action 的成本来自把 Tile Memory 内容写回系统内存。
- 这套判断不改变画面结果,改变的是一次 render pass 需要多少外部带宽。
(03:57)演讲反复强调,应用能明确控制的是 load/store action。只加载需要的数据,只保存后续还要用的数据。对 TBDR 来说,这直接决定 Tile Memory 和系统内存之间发生多少传输。
HSR 先算可见性,再运行 fragment shader
(04:39)Hidden Surface Removal(HSR,隐藏面移除)依赖片上深度缓冲,在 fragment shader 运行前记录每个像素最前面的 primitive。即使 opaque 三角形按 back-to-front 提交,GPU 也可以先得到可见层,再只给最终可见的像素跑 shader。
工作流示例:
- 先提交 opaque geometry。
- 再提交 alpha test、discard 或 depth feedback 相关 geometry。
- 最后提交 translucent geometry。
- 避免把 opaque mesh 与非 opaque mesh 交错提交。
关键点:
- HSR 会先记录每个像素最前面的 primitive,再运行 fragment shader。
- translucent primitive 会迫使 GPU flush 被覆盖的像素,过早提交会增加无效着色。
- opaque mesh 之间不需要严格排序,按 visibility state 分组更重要。
(07:27)这里的 overdraw 指每个像素触发 fragment shader 的次数。为了降低 overdraw,session 给出的实践是按 visibility state 分组:opaque、alpha test/discard/depth feedback、translucent。opaque mesh 之间不要求严格排序,但不要和非 opaque mesh 交错。
Programmable Blending 和 memoryless target 避免中间纹理落地
(09:27)全屏雾效、deferred lighting 这类算法常常需要自定义混合。传统做法会把多个 attachment 写出,再在后续 pass 作为纹理采样。TBDR 暴露 programmable blending,让 fragment shader 直接访问 Tile Memory 中的像素数据,从而把多个 render pass 合并。
概念示例:
- deferred lighting 先得到表面属性,再用这些属性计算光照。
- 传统多 pass 做法会把属性 attachment 写出,再在下一轮 pass 采样。
- Programmable Blending 让 fragment 阶段直接访问 Tile Memory 中同一像素的数据。
- 这样可以把原本分开的 pass 合并,减少中间 attachment 的 load/store。
关键点:
- session 明确把 global fog、deferred lighting 归为需要 custom blending 的场景。
- Programmable Blending 的作用范围是当前像素,适合单像素级的混合和光照累积。
- 它的收益来自省掉中间 render target 的外部写出和读回。
(10:28)如果某些 render target 只在 Tile Memory 内使用,就可以把它们设成 memoryless storage。这样 Metal 不为这些临时附件分配可持久保存的系统内存。
工作流示例:
- 列出 deferred renderer 中的 G-Buffer attachment。
- 标出只在当前 render pass 内使用的 attachment。
- 这些 attachment 不需要保留到系统内存中。
- 最终颜色或后续 pass 还要读取的结果,才需要持久保存。
关键点:
- Memoryless Render Targets 针对的是只存在于 Tile Memory 的临时数据。
- 它减少的是内存占用,因为不再为这些临时 render target 分配外部存储。
- 如果后续 pass 需要采样某个结果,它就不属于这个 session 所说的 memoryless 场景。
MSAA 的 resolve 从 Tile Memory 完成
(11:31)Apple GPU 的 MSAA(Multisample Antialiasing,多重采样抗锯齿)会在片上保存多个 sample,并在 tile flush 时 resolve。session 给出的结论很直接:多重采样纹理通常不需要 store,只需要把 resolve 结果写到最终纹理。
工作流示例:
- render pass 内使用 MSAA 生成多个 sample。
- 多个 sample 保存在 Tile Memory 中。
- tile flush 时直接 resolve 成最终结果。
- 只保存 resolve 后的纹理,不保存完整 multisample attachment。
关键点:
- session 明确说 MSAA Resolve always happens from Tile Memory。
- 多重采样 attachment 在这种流程里是 transient 数据。
- 把它和 memoryless storage 结合,可以同时节省带宽和 footprint。
Imageblocks 和 Tile Shaders 让 compute 进入 render pass 中间
(15:53)从 A11 开始,Apple GPU 增加了 on-chip Imageblock 和 Tile Compute。Imageblock 是 Tile Memory 中的二维数据结构,可以被 fragment function 或 kernel function 访问。Tile Shader 是能在 render pass 中 dispatch 的 compute kernel,按 API 提交顺序和 draw call 交错执行。
工作流示例:
- 先在 render pass 内得到 tile 对应的图像数据。
- 用 Imageblock 表示 Tile Memory 中的二维数据。
- 在 draw call 之间插入 Tile Shader dispatch。
- Tile Shader 按提交顺序与 draw call 交错执行,并和前后的 draw call 建立同步边界。
关键点:
- Imageblock 的价值在于 GPU 知道它正在处理图像数据。
- Tile Shader 可以在 mid-render pass 访问 Imageblock。
- 这让一些原本需要额外 compute pass 的步骤留在同一个 render pass 内完成。
(19:28)tiled deferred rendering 可以用 Tile Shading 把 light culling、G-Buffer 使用和 lighting 累积合到单个 pass。原本三个 pass 之间的外部纹理读写,变成 Tile Memory 内的交错渲染与计算。
Argument Buffers 和 Indirect Command Buffers 把 draw 决策放到 GPU
(20:47)传统 render loop 由 CPU 遍历场景、读取上一帧 GPU 写出的遮挡信息、做视锥裁剪和 LOD 选择,再编码 draw command。这里有同步点:CPU 必须等待 GPU 结果,GPU 又要等 CPU 提交下一批 draw。
工作流示例:
- 把 scene 中的 mesh、material、model 关系整理成 GPU 可以遍历的数据结构。
- GPU 先遍历场景,渲染 occluder。
- GPU 再遍历场景,执行 culling 和 LOD selection。
- GPU 最后提交可见对象的 draw commands,完成最终渲染。
关键点:
- Argument Buffers 负责让复杂场景数据留在 GPU 可访问的结构中。
- Indirect Command Buffers 负责让 GPU 生成自己的 draw commands。
- 这条路径去掉了 CPU 读取上一帧遮挡数据后再编码 draw command 的同步点。
(22:45)session 的 GPU-driven render loop 是三步:GPU 遍历场景渲染 occluder,GPU 再遍历场景做 culling 和 LOD selection,最后 GPU 渲染最终场景。CPU 不再逐帧决定每个 draw call。
核心启发
-
做什么:给现有 deferred renderer 增加 memoryless G-Buffer。 为什么值得做:session 明确指出 deferred rendering 的成本来自中间纹理的写出和采样,memoryless render target 可以减少 footprint。 怎么开始:检查每个 G-Buffer attachment 的生命周期,只在同一 render pass 内使用的附件设置
MTLStorageMode.memoryless,最终颜色附件保留.store。 -
做什么:把渲染队列按 visibility state 分组。 为什么值得做:HSR 对 opaque geometry 很高效,透明和 discard 会触发 flush,错误顺序会增加 overdraw。 怎么开始:在提交 draw 前把 mesh 拆成 opaque、alpha test/discard/depth feedback、translucent 三组,先保证 opaque 与非 opaque 不交错。
-
做什么:把 MSAA 中间采样纹理改成 transient 资源。 为什么值得做:Apple GPU 的 MSAA resolve 从 Tile Memory 完成,完整 multisample texture 没有必要写回系统内存。 怎么开始:让 multisample texture 使用 memoryless storage,render pass 里设置
resolveTexture,store action 使用.multisampleResolve。 -
做什么:为多光源场景试做 tile-based light culling。 为什么值得做:Tile Shading 可以在 render pass 内按 tile 处理 light list,把多个 pass 合成一个更省带宽的流程。 怎么开始:先把每个 tile 需要的 light index 列表组织出来,再用 tile shader 读取 imageblock 里的 G-Buffer 数据做光照累积。
-
做什么:把大场景的 culling 和 LOD 选择迁移到 GPU。 为什么值得做:Argument Buffers 让 GPU 遍历复杂场景数据,Indirect Command Buffers 让 GPU 编码 draw command,能减少 CPU/GPU 同步点。 怎么开始:把 mesh、material、model flatten 成 argument buffer 数组,用 compute pass 写入 indirect command buffer,再在 render pass 执行这些 command。
关联 Session
- Optimize Metal apps and games with GPU counters — 学会用 Xcode 12 的 GPU counters 验证 TBDR 优化是否真的降低了瓶颈。
- Bring your Metal app to Apple silicon Macs — 从 Mac 移植角度继续讲 Apple GPU 的 TBDR 架构和 Metal 适配。
- Optimize Metal Performance for Apple silicon Macs — 针对 Apple silicon Mac 的 Metal workload 做进一步性能调优。
- Build customized ML models with the Metal Performance Shaders Graph — 了解 Metal 计算能力如何服务机器学习图和张量工作负载。
评论
GitHub Issues · utterances