WWDC Quick Look 💓 By SwiftGGTeam
Harness Apple GPUs with Metal

Harness Apple GPUs with Metal

观看原视频

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 放回硬件背景里看。loadActionstoreAction、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。

核心启发

  1. 做什么:给现有 deferred renderer 增加 memoryless G-Buffer。 为什么值得做:session 明确指出 deferred rendering 的成本来自中间纹理的写出和采样,memoryless render target 可以减少 footprint。 怎么开始:检查每个 G-Buffer attachment 的生命周期,只在同一 render pass 内使用的附件设置 MTLStorageMode.memoryless,最终颜色附件保留 .store

  2. 做什么:把渲染队列按 visibility state 分组。 为什么值得做:HSR 对 opaque geometry 很高效,透明和 discard 会触发 flush,错误顺序会增加 overdraw。 怎么开始:在提交 draw 前把 mesh 拆成 opaque、alpha test/discard/depth feedback、translucent 三组,先保证 opaque 与非 opaque 不交错。

  3. 做什么:把 MSAA 中间采样纹理改成 transient 资源。 为什么值得做:Apple GPU 的 MSAA resolve 从 Tile Memory 完成,完整 multisample texture 没有必要写回系统内存。 怎么开始:让 multisample texture 使用 memoryless storage,render pass 里设置 resolveTexture,store action 使用 .multisampleResolve

  4. 做什么:为多光源场景试做 tile-based light culling。 为什么值得做:Tile Shading 可以在 render pass 内按 tile 处理 light list,把多个 pass 合成一个更省带宽的流程。 怎么开始:先把每个 tile 需要的 light index 列表组织出来,再用 tile shader 读取 imageblock 里的 G-Buffer 数据做光照累积。

  5. 做什么:把大场景的 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

评论

GitHub Issues · utterances