现代图形API中的Load Store Action和MemoryLess
前言
现代图形 API 给用户开放了愈来愈多的底层接口,让用户更好的优化渲染性能,而对于移动端来说,内存和带宽是非常重要的指标,本文就是对 Load Store Action(以下简称 LSA)和 MemoryLess 这几个重要的性能优化项进行总结提炼
Load Store Action
Vulkan 和 Metal 这两个图形 API 都显式开放了 LSA 设置,而 OpenGL 则可以通过特定的指令来设置 LSA,一般游戏引擎 Unity 和 UE 都已经在 RHI 层封装好了,只需要在业务层进行统一设置即可
那么 LSA 到底是什么
LSA 介绍
Load/Store Actions 是图形 API 中在 RenderPass 切换的时候,指定如何处理图像内容的操作
- 从 SystemMemory 拷贝数据到 TileMemory 是 Load Action。
- 从 TileMemory 拷贝数据到 SystemMemory 是 Store Action。也称为 Resolve。
- OpenGLES 中可以通过 glInvalidateFramebuffer 来规避上述 Load 和 Store。
- Metal 和 Vulkan 中可以通过 RenderTexture 的 loadAction 和 storeAction 的设置来控制 Load/Store。
- loadAction 有三种,dontCare,load,clear。
- storeAction 有四种,dontCare,store,multisampleResolve, storeAndMultisampleResolve。
Load Action
DontCare
不需要渲染目标的先前内容,而是渲染到其所有像素。 选择 dont Care
。此操作不产生任何成本,并且在渲染过程开始时像素值始终未定义。
这意味着如果只是渲染部分区域,则会导致另外一部分的 RT 数据不可知,例如
Clear
不需要渲染目标的先前内容,并且仅渲染部分像素。 选择 clear
。此操作会产生将渲染目标的清除值写入每个像素的开销。
Load
确实需要渲染目标的先前内容,并且仅渲染部分像素。 请选择 load
。此操作会产生从内存加载每个像素先前值的开销。此操作比 dont Care
或 clear
慢得多。
适用于仅渲染部分画面,进行叠加,例如先渲染天空盒,再渲染场景物体,就需要 Load
Store Action
Dont Care
无需保留渲染目标的内容。 请选择 dont Care
。此操作不会产生任何开销,并且像素值在渲染过程结束时始终未定义。对于在渲染过程中使用但之后不需要的中间渲染目标,请选择此操作。这通常是深度和模板渲染目标的正确操作。
需要注意,如果 DontCare 了 Store,那么下次即使 Load,也不会获取此次绘制的内容
Store
确实需要保留渲染目标的内容。 请选择 store
。此操作会产生将每个像素的值存储到内存的开销。对于可绘制对象来说,这始终是正确的操作。
如果渲染目标是一个多重采样纹理。 执行多重采样时,您可以决定存储渲染目标的多重采样数据还是已解析数据。多重采样数据存储在渲染目标的 texture
属性中。已解析数据存储在渲染目标的 resolve Texture
属性中。请参阅下表,选择多重采样时的存储操作
渲染目标是一个多重采样纹理。 执行多重采样时,您可以决定存储渲染目标的多重采样数据还是已解析数据。多重采样数据存储在渲染目标的 texture
属性中。已解析数据存储在渲染目标的 resolve Texture
属性中。请参阅下表,选择多重采样时的存储操作:
Multisampled data stored | Resolved data stored | Resolve texture required | Required store action |
---|---|---|---|
Yes | Yes | Yes | MTLStoreAction.storeAndMultisampleResolve |
Yes | No | No | MTLStoreAction.store |
No | Yes | Yes | MTLStoreAction.multisampleResolve |
No | No | No | MTLStoreAction.dontCare |
实践示例
可以在多个渲染过程中使用相同的渲染目标。对于任意两个渲染过程之间的同一渲染目标,可能存在多种加载和存储组合,具体取决于以下哪种场景描述了您的渲染目标在不同渲染过程中的需求。
在下一个渲染过程中,不需要渲染目标的先前内容。 在第一个渲染过程中,选择 MTLStore Action .dont Care
以避免存储渲染目标的内容。在第二个渲染过程中,选择 MTLLoad Action .dont Care
或 MTLLoad Action .clear
以避免加载渲染目标的内容。
在下一个渲染过程中,您确实需要渲染目标的先前内容。 在第一个渲染过程中,选择 MTLStore Action .store
、 MTLStoreAction.multisampleResolve
或 MTLStoreAction.storeAndMultisampleResolve
来存储渲染目标的内容。在第二个渲染过程中,选择 MTLLoad Action .load
来加载渲染目标的内容。
First rendering pass store action | Second rendering pass load action |
---|---|
DontCare |
One of the following actions: DontCare Clear |
One of the following actions: Store MultisampleResolve storeAndMultisampleResolve |
Load |
性能优化示例
MemoryLess
在 Unity 中,仅 Metal 和 Vulkan 图形 API 支持 MemoryLess,其余 API 会 fallback 为正常 RT
一图以蔽之,仅在 GPU Tile Memory(On-Chip)上的 RT,CPU 不可访问
顺带介绍下几个常用模式
- 仅通过 GPU 访问,数据归 GPU 所有。请使用
MTLStorage Mode .private
。如果您通过计算、渲染或位块传送过程将资源填充到 GPU,请选择此模式。这种情况常见于渲染目标、中间资源或纹理流式传输。有关如何将数据复制到私有资源的指导,请参阅 将数据复制到私有资源 。 - 在 CPU 上填充并在 GPU 上频繁访问,CPU 和 GPU 共享集成内存。使用
MTLStorage Mode .shared
。 - 单个 RenderPass 的临时纹理内容,GPU 为在渲染过程中或渲染之间使用的纹理占用的内存。请使用
MTLStorage Mode .memoryless
。Memoryless 模式仅适用于纹理,并将临时资源存储在 tile 内存中以实现高性能。例如,深度纹理或模板纹理仅在单次渲染过程中使用,在渲染前后阶段都不需要使用。如果想要跨 Renderpass 使用,必须 Store 到 System Memory
MSAA
之所以将 MSAA 分类到这,是因为 MSAA 中的 MultiSample 过程一般而言可以在 Chip Memory 上完成,设置 MemoryLess + (StoreAction: Dont Care)可以极大节省内存和带宽
无优化渲染 MSAA:
Store 优化:
MemoryLess 优化
可以看到直接节省了 4xMSAA 的内存和带宽大小,整个 Resolve 过程都在 Tile Memory 完成
Depth 和 Stencil 也是同理:
引用
https://community.arm.com/arm-community-blogs/b/mobile-graphics-and-gaming-blog/posts/picking-the-most-efficient-load-store-operations
https://developer.apple.com/documentation/metal/setting-load-and-store-actions/
https://developer.apple.com/videos/play/wwdc2020/10631/
https://docs.unity3d.com/ScriptReference/RenderTexture-memorylessMode.html
https://speakerdeck.com/codelynx/tailor-your-metal-apps-for-apple-m1