大多数浏览器和
Developer App 均支持流媒体播放。
-
打造优化的 Metal App 和游戏
对于资源密集型 Metal app 和游戏来说,优化性能、内存和带宽非常重要。了解简化渲染并实现高帧率的主要最佳做法。了解有助于查明高开销或异常 GPU 工作的强大工具。深入了解可带来性能提升的 GPU 功能,并获取关于高效使用内存的专家指导。
资源
相关视频
WWDC23
WWDC21
WWDC20
WWDC19
-
下载
(提交优化的Metal app 和游戏)
早上好 欢迎大家来参加演讲 我是Guillem Vinals Gangolells 来自Metal生态系统团队 去年我们和许多游戏开发人员 一起工作 并认识到了一些常见问题 结果是我们决定把它们放在一起 说一下 今天我总共会讲18个最佳实践 帮助你改善你的Metal app
请注意 我不会展示一些不错的范例 或可接受的范例 非常肯定不是那些能迎刃而解的范例 今天的主要内容是关于 Metal最佳实践 请按照最佳实践去做 (Metal最佳实践) 在我们开始之前 我想感谢来自 Digital Legends的朋友们 Digital Legends 是巴塞罗那的一支非常有才华的团队 他们开发了Afterpulse Afterpulse 是一个精美的游戏 它使用了许多渲染技术 比如纹理映射 功能完备的延迟渲染 以及后置处理 比如全屏泛光和FXAA 我们将在本场演讲中 全程使用 Afterpulse作为例子 用于演示我们的工具 并突出一些最佳实践 (Afterpulse - 精英部队) 我们把这场演讲分为三个部分 通用性能 内存带宽 和内存占用 让我们从通用性能开始讲 (通用性能) 在这部分中 我会讲选择正确的分辨率 避免过度绘制 减少所写入的每个像素的 片段着色器调用次数 我们还会讲GPU提交、资源传输 以及热感 (最佳实践) 让我们开始讲第一个最佳实践 选择正确的分辨率 请注意你游戏中的每个效果 可能都需要不同的分辨率 因此考虑每个分辨率的图片品质 和性能权衡非常重要 并谨慎选择 同时以原生分辨率 或接近原生的分辨率结合游戏UI 从而使UI看起来清晰 无论显示尺寸是什么
我们可以用Metal帧调试器 来检查分辨率
在Metal帧调试器内 我们希望使用依存查看器 它会显示每个渲染通道的图表 在这个例子中 我们使用Metal示例app 要获取依存查看器 我们要选择命令缓冲区
在这个例子中 你可以看到 这个示例Metal app 使用了一些效果 分辨率都不同 比如阴影映射和SSAO 与主光通道的分辨率不同 同时UI由原生分辨率合成 因此它总是看起来很尖锐
这些分辨率看起来不错 对于这个Metal app来说 性能也不错 我们希望你能在做你的游戏时 做出类似的选择 让我们继续看第二个最佳实践
最小化不透明过度绘制 过度绘制是片段着色器 对每个写入的像素的处理次数 iOS GPU极其擅长减少 不透明过度绘制 我们只需要提供一丁点帮助
这里的最佳实践是 首先渲染不透明网格 稍后渲染半透明网格 同时不要渲染全透明或不可见的网格 我们将再次使用Metal帧调试器 来帮助我们 这一次我们将查看GPU计数器 用于验证指定渲染通道的过度绘制 我们聚焦于 Metal Demo app的 主光通道
为了计算过度绘制 我们将用片段着色器调用次数 除以像素存储器数量 我们可以使用底部的筛选条 迅速查找我们所寻求的性能计数器
在这个例子中是全透明情境 没有其它需要验证的东西 没有过度绘制 我们准备好继续了 我们也要求你们在你的游戏中 做类似的选择 并验证你也准备好继续了 那么让我们继续讲下一个最佳实践
提早提交GPU工作
提早安排好后台GPU工作非常重要 它将改善游戏的延迟性和响应性 并允许系统更好地适应工作量 因此 在帧中拥有多个GPU提交 非常重要 特别是你要在获得可绘制物之前 提早提交GPU 并且有些可绘制物会拖延渲染 当你在帧中获得可绘制物之后 最好是尽可能晚的获得它们 然后你就会出现延迟GPU提交 你需要安排好所有前台工作 潜在的有UI合成通道
因为要解释这个问题有点棘手 我准备了一个演示 因此让我们快速看一个演示 我会告诉你如何用Metal 系统追踪进行识别 (演示)
很酷 我们在这里看到的是 Afterpulse的一个帧 是我们去年在老版游戏上捕捉到的 这是用游戏性能模板捕捉到的 我们去年引入了这个模板 因此你已经熟悉其中一些工具了 今年我们新添加了热状态 以及Metal资源分配 我们稍后会讲到这个 现在我们主要讲潜在的问题 在这个例子中我们想查看显示面板 并了解底部是否有大量事件 界面显示在前台的时间 是否比预期时间要长 那么也许这是个问题 我们应该解决它 我们要做的就是进入其中一个区域 按住“选项”键 并在这个区域上拖动游标
那么在这个例子中 我们可以看到有两个帧 看起来比预期的要晚 同时GPU有很长时间都处于 闲置状态 那可能就是导致 我们正在尝试调试的问题的原因 那么让我们具体看一下 我们通过披露你已经熟悉的 Metal系统追踪中的 所有追踪来实现 我们在这里所看到的是 app是如何编码的 以及GPU如何处理这个工作 让我们聚焦于这个橘色帧 正如你在这里所看到的 app编码了大量工作 因此我们有阴影映像、延迟通道 以及部分全屏反光链等等 因此… GPU实际上并没有处理那个工作 同时 可绘制物正在出现危机 因此这就是导致这个 闲置缺口的原因 但我们已经编码了一些工作 我们只是不能提交它 这就是最佳实践 你在获得可绘制物之前 提交全部提前编码的后台工作 因此 方案是执行一次GPU提交 请注意只有一次GPU提交 我们可以从这里的帧末端看到 所有GPU工作都发生在最后
谢天谢地 这个很容易解决 Digital Legends 其实已修复了它 让我们看看Afterpulse 的新追踪 看看它看起来怎么样
好的 那么 让我们具体来看 在这个例子中 我们可以看到没有闲置时间 当我们正在等待获得可绘制物时 因此让我们披露 来自Metal系统追踪的追踪 正如我们之前所做的那样 在这个例子中 我们可以看到GPU 正在处理我们在获得可绘制物之前 安排好的工作 因此在这个例子中 闲置时间较少 因此这就允许系统 更好地适应工作量 并且不会出现任何问题
做完这个操作之后 我们就知道我们已经准备好继续了 并且我们已经拥有多个GPU提交 并且不会导致任何拖延 因为我们在等待获取可绘制物之前 就实现了这个操作 让我们返回到幻灯片中 (演示) 好的 修复那个问题其实非常简单 比解释这个问题简单多了 我们要做的就是 简单地给每个帧 创建多个命令缓冲区 因此我们首先要创建命令缓冲区 来编码所有后台工作 那将作为提早的GPU提交 我们将委托命令缓冲区 然后等待下一个可绘制物 那将会拖延我们的线程 在我们获得可绘制物之后 我们将创建最后一个命令缓冲区 我们将在命令缓冲区中 编码所有前台工作 并呈现可绘制物 这将作为最后一次GPU提交 并且这还将确保帧的工作流程 真的足够好
就那么简单 只需要使用多个命令缓冲区即可 让我们继续看第四个最佳实践: 高效地传输资源 资源分配真的很占时间 并且从渲染线程中传输资源 可能会导致拖延 因此 这里的最佳实践是考虑内存 和资源传输算法的性能权衡 并确保你在启动时分配并加载 GPU资源 因为你不需要在运行时分配它们 任何需要在运行时传输的资源 请一定要在专用线程上这样做 这样做可以避免拖延 这非常重要
我们稍后再重新讲资源传输 在内存占用部分 以及重新评估内存和性能权衡 现在 让我们使用Metal 系统追踪来调整资源传输 今年我们新增加了分配追踪 那会为你显示每一个 资源分配的事件 以及同一时间线上 所有其他工具的分配 这将允许你识别 从主渲染线程上传输的 所有可能导致拖延的资源 (Metal系统追踪) 还有一个应该调整的东西即热感 这对于保持游戏的持久性能来说 非常重要 这会改善系统的整体温度 以及游戏的稳定性和响应性 最佳实践就是在严峻的热状态下 测试游戏 同时考虑针对这种严峻的热状态 调整游戏 因为那很有可能会帮助你了解热节流 (最佳实践) 今年我们在Xcode中新增加了 设备状况 那将允许你直接从设备的窗口中 设置严峻的热状态 在底部有一场演讲 我们将具体讲这个话题 我建议你们来参加 (设备状况) 我们还可以使用Xcode能量计 来验证设备运行时所处的热状态 在这个例子中 我们让设备处于严峻的热状态下运行 并且我们有效地验证了 设备正在陷入严峻的热状态 只需要数秒钟就可以 上升到那个温度 (Xcode能量计) 很酷 现在让我们继续 演讲的第二个部分 在这个部分中 我们会讲内存带宽 (内存带宽) 内存带宽非常重要 那是因为内存传输非常昂贵 它们消耗能量并产生热量 为了帮助减轻这种现象 iOS设备在CPU和GPU之间 拥有共享系统内存 以及GPU专用的 Tile Memory Metal会帮助你对两者进行平衡 因此现在让我们从纹理开始看起 (纹理) 纹理采样很可能是游戏中 主要消耗带宽的功能 因此我们有一些最佳实践 从而让你正确地配置纹理 在这个部分中 我们要讲游戏资产的离线纹理压缩 GPU纹理压缩 以及如何选择正确的像素格式 让我们从纹理资产开始讲 (最佳实践) 压缩纹理资产非常重要 那是因为大纹理的采样效率可能很低 并且资产可能也是任意大 因此确保它们都被压缩了 并且 还为所有可能被缩小了的纹理 生成了纹理映射 让我们看一下纹理压缩如何节约内存 (纹理压缩的内存节约) 这是Afterpulse 最大纹理中的一个 如果我们不压缩这个纹理 它可能需要占用16MB的内存 才能加载出来
通过使用纹理压缩 我们可以把它降低到 小于3MB 包含完整的纹理映射链 那极大地节约了内存 但请注意 Afterpulse 使用了PVRTC 因为它在A7设备上运行 比如iPhone 5S 如果你的游戏投放范围是 较新的设备 请使用STC 因为它在图片品质方面 提供更好的压缩率
为了验证我们的资产是否进行了 正确的压缩 我们可以使用内存查看器 (Metal内存查看器) Metal内存查看器 是个很棒的工具 我将在内存占用部分 对它进行完全介绍 现在我们要用它来检验 我们的全部资产 我们可以双重检验它们是否被压缩、 被映射以及看起来是否还不错
但对于那些不能提前压缩的纹理来说 会怎么样 比如渲染目标或运行时 生成的所有纹理? (无损耗纹理压缩) 最新的iOS GPU 支持无损耗纹理压缩 允许GPU压缩纹理 用于实现较快的访问速度
因此 下一个最佳实践是优化纹理 从而GPU可以拥有较快的访问速度 正确地配置纹理非常重要 你可以使用私有存储模式 因此只有GPU拥有 对纹理数据的访问权限 并允许它对内容进行优化 同时 不要设置未知的使用标志 并避免设置不必要的使用标志 比如shaderWrite或pixelView 因为那些可能会禁用压缩功能 (最佳实践) 可通过CPU和GPU 访问的共享纹理 应该当每次CPU升级数据后 进行明确的优化 这也非常重要 让我们看看如何用几行代码 来实现这两个操作 真的只需要几行代码 要创建最优纹理 我们要把存储模式设为私有 因此只有GPU有访问权限 并且我们要设置 明显但却保守的使用标志
在这个例子中 我们要把纹理作为中间的渲染目标 从而我们不需要任何其它使用标志
但对于共享纹理来说怎么样呢? 嗯 那更加棘手 因此 CPU和GPU可共同访问 共享纹理 因此如果CPU更新了纹理的一部分 或任何纹理数据 我们可能需要明确要求GPU 优化它的内容 请注意这里有一个权衡 在CPU更新数据的次数 和GPU稍后需要访问它的次数之间
内存查看器实际上是一个很好的工具 它可以帮助我们解决这个问题 那是因为我们可以配置内存查看器 使其显示所有纹理的存储模式 和使用标志 从这一个屏幕上我们可以看到 压缩纹理 并识别所有纹理的配置都正确 它是个很棒的工具 从这点上来说我们几乎已经完成了 对纹理的配置 我们只需选择正确的像素格式即可
较大的像素格式使用较多的带宽 因此最佳实践就是 避免使用带有不必要频道的像素格式 并尽可能尝试降低精度
但请注意 采样率自身 也取决于像素格式 在这个例子中 我们可以看到像素格式如何 直接影响 GPU的纹理采样率 你要特别注意128位格式 比如RGBA 32位浮点型 因为 那是以四分之一的比率进行采样的 这些高精度格式经常用于噪音纹理 或为后处理效果查找表
再一次 我们可以使用内存观察器 来帮助我们解决那个问题 内存观察器将允许我们 按名称或像素格式筛选纹理 因此我们将验证 Metal Demo 对SSAO实施使用了16位格式 这对于噪音纹理来说非常重要 (Metal内存观察器) 还有一点要注意 在这个例子中 绝大多数纹理实际上是渲染目标 因此随着游戏变得越来越复杂 作为渲染目标使用的纹理 实际上可能会消耗大量带宽 让我们具体看一下
在这个部分中 我们将回顾 渲染路径加载和存储操作 密切关注MSAA并稍微提一下 Tile Memory 让我们从优化加载和存储操作开始讲
你应该避免加载或存储 不必要的渲染目标 渲染目标加载和存储操作 非常容易被忽略 但可能很快就会成为一个问题 请一定要检查它们 实际上很简单 只需要几行代码 在这个例子中 我们配置了一个渲染路径描述符 我们希望那个颜色缓冲区是临时的 意思就是我们不想从它里头 加载或存储任何东西 它与设置正确的加载和存储操作 一样简单 我们希望加载操作明确 从而以后不需要转换到 芯片级GPU 我们希望存储操作是 DontCare 因此不需要在渲染路径末端写入数据 就这些了 验证我们拥有对的东西 也非常简单 我们可以使用依存查看器
在这个例子中 Metal示例存储了颜色缓冲区 即使以后用不到了 依存查看器会显示一个问题图标 来突显这个问题 我们应该设置 在刚才的幻灯片上所看到的 DontCare存储操作 问题就解决了 真的就是这么简单
这对于多重采样的渲染目标来说 尤其重要
iOS设备的MSAA非常快 那是因为Tile Memory 不会消耗任何额外的带宽 这也允许我们 声明多重采样的纹理完全是临时的 事实上我们起初甚至不需要 让系统内存分配 对它进行备份 因此最佳实践是 仔细考虑MSAA的原生分辨率 因为效率很高 同时 一定不要加载或存储 多重采样纹理 因为你把多重采样纹理的存储模式 设为了不耗内存 我稍后会具体介绍不耗内存 现在让我们看看 如何配置多重采样纹理 以及它所使用的渲染路径
在这个例子中我们只需要设置 不耗内存存储模式 并确保渲染路径使用它 清理它的内容并丢弃采样 我们只希望从多重采样纹理中 解决问题 我们不需要存储它 我们不想存储中间数据 应该只存储最终解决的纹理 我们可以再一次使用依存查看器 来帮助我们验证我们做了正确的操作
在这个例子中 Metal示例正在加载 和启动多重采样纹理 损耗很大 在设置正确的标志之后 正如我刚才给你演示的那样 我们将节约超过85MB的内存带宽 以及内存占用 这对于验证多重采样缓冲区来说 非常重要 但请注意 产生这样的节约只因为 因为我们通过使用MSAA 暗中权衡Tile Memory 因此下一个最佳实践是 明确权衡Tile Memory Metal针对某些功能提供 Tile Memory的访问权限 比如程控混合、图像块和扁平着色器 最佳实践就是明确利用它 特别是实施更先进的渲染技术 在使用Metal 执行现代化渲染演讲中 具体讲了其中一些技术 现在我们只快速了解一下延迟着色 (传统的延迟着色) 延迟着色被认为很消耗带宽 那是因为从传统上它要求app 存储集合图形信息 或G-Buffer 因为一组纹理代表许多像素属性 然后在第二光通道中采样那些纹理 最终颜色堆积在渲染目标中 请注意我们存储 再从G-Buffer加载全部数据 因此这就是为什么会消耗带宽的原因
iOS让你效率更高
(单一路径延迟着色) 在iOS上 我们可以权衡程控混合 它允许片段着色器 直接从Tile Memory 访问像素数据 这意味着G-buffer数据 可存储在Tile Memory上 并通过全部光积聚着色器在同一个 渲染路径中进行访问 它是个强大的功能 Digital Legends 已经使用了很多年 这就是通过依存查看器看到的 Afterpulse的 单一通道延迟渲染 很漂亮 四个G-Buffer缓冲区 是完全临时的 只存储了最终颜色和深度 不仅漂亮 而且高效 现在请让我们欢迎 Samuel上台来 演示依存查看器 (演示)
谢谢Guillem 我们刚才了解了老版的 Afterpulse 想看看是否可以进行一些优化 来改善它的性能
现在我要使用Metal 帧调试器中的依存查看器 来讲一下Guillem 刚才提到过的一些问题
要开始 让我们点击 CommandBuffer 打开依存查看器
依存查看器给我们显示了 由app编码的所有GPU通道
我们可以看到Afterpulse 有一个CommandBuffer 并以渲染阴影图开始它的帧 然后是相位延迟
这会注入亮度计算 粒子模拟和布尔链 并由最终屏幕通道使用
今年依存压缩器变得更紧凑了 如果你有群 那从高层级看帧如何被渲染 非常简单 我们甚至可以更深入到任意群 如果我们想了解更多详情的话 因此 实际上这个布尔链 其实是12通道的
现在依存查看器是查找 Guillem刚提到过的 某些问题的好地方 并且我们可以在这个 最终屏幕通道上看到一些问题 让我们点击问题图标查找更多问题
看起来与 Guillem之前提到过的 一个加载存储操作问题很像 他们已经把存储操作设置为存储 但他们没有在帧中再次使用这个纹理 因此这个问题建议我们 应该把它设为DontCare 然后我们将节约差不多14MB 内存带宽 针对这两个联合的纹理
今年我们把这个过程变得更简单了 可以从图表中查找所有问题 只需要点击右下方的新问题按钮 即可获得一个问题列表
Guillem刚才提到的 另一个最佳实践是 选择正确的像素格式 因此今年在iOS上 我们引入了新的16深度格式 让我们使用新搜索来查找 32深度纹理
看起来它们占用了36MB内存 针对阴影图纹理 因此当Digital Legends团队 在WWDC结束返回巴塞罗那后 他们研究使用这个新格式 可能会节约一半的内存 如果他们的阴影需求允许的话
如果我们继续通过图表进行搜索 你可以看到由大量潜在的内存节约
如果你使用Metal帧调试器中的 依存查看器 来查找和诊断老版 Afterpulse中的一些问题 Digital Legends 其实已经做了一些改善 让我们快速看一下
我们可以立即看到 它们现在使用了多个命令缓冲区 那么这将修复Guillem 之前展示给我们的问题 CPU因为要等待下一个可绘制物 而被阻塞 而GPU处于闲置状态
如果我们具体看一下最终屏幕通道 我们可以看到他们已经修复了 存储操作问题 事实上 因为这两个纹理是完全临时的 并且他们已经把存储模式设置为 不耗内存 他们将不使用任何系统内存
因此 依存真的是一个 开始调试渲染管线的好地方 舞台交还给Guillem 他会继续讲 关于优化app内存占用的最佳实践
(演示) 谢谢Sam 演示很棒 我希望你们也可以使用依存查看器 很酷 那么让我们继续进入 演讲的最后一个部分 内存占用 (内存占用) 内存占用其实对于你的游戏来说 非常重要 那是因为iOS强制执行 严格的app内存限制 以便保持系统在app上的响应性 (app内存限制) 有些人可能已经注意到了 iOS 12在内存计算方面 做了一些改变 计算方式的修改影响了绝大多数 Metal资源 Metal资源 比如缓冲区或纹理 也许是大多数app的内存占用 因此衡量游戏的内存占用对你来说 非常重要 你可以使用Xcode内存计 (Xcode 11) Xcode内存计 会报告系统所测量的 游戏的内存占用的数值 用它来验证游戏的状态非常重要 今年它还将显示app内存限制 如果游戏所占用的内存 接近所限制的内存的话
但如果我们想特别关注 我们的Metal资源所使用的内存 怎么办?
今年我们引入了内存查看器 我们把它添加到 Metal帧调试器中了 (Metal内存查看器) 内存查看器自身有两个部分 第一部分是顶部的条形图 显示按类别显示资源 比如按类型、存储模式和用法 我们还可以用这个条形图 在最大的资源中进行快速导航 并在显示时把它们突显出来 第二部分是底部有一个表 显示我们所筛选的资源 它包含资源类型的一些专有属性 比如纹理的像素格式和分辨率 底部还有一个筛选条 可以帮助你做进一步的筛选 它是个强大的工具 我们希望你可以利用它 来了解全部GPU资源的内存占用
同时 我们今年引入的另一个 强大的工具是 Metal资源分配工具 (Metal资源分配工具) 它有三个不同的组成部分: Metal资源分配追踪 显示游戏的当前Metal内存占用 分配追踪 针对每个资源分配和再分配 显示一个事件 以及一些信息
还有详情表视图 显示所捕捉到的所有分配的更多信息 这些工具都很强大 让你很好地了解 游戏中的内存占用概况 以及内存占用如何随时间改变
但有些人还要求添加其它功能 特别是其中有一个功能 你们已经要求很久了 今年我要很高兴地向大家宣布 我们有基于C的API 用于在运行时查询可用的内存 这会让你的游戏更有效地传输资源 并避免内存尖峰 内存尖峰将导致游戏 超出app内存限制 (可用的内存) 今年我们引入的 另一个很酷的API是 设备上的GPU捕捉 允许你以编程方式触发GPU捕捉 不需要Xcode 我们认为它对处于QA过程的 游戏测试人员来说是个理想的工具 要启动它 你只需要向info.plist中添加 MetalCaptureEnabled即可 非常简单 (设备上的GPU捕捉) 现在让我们看看如何把这两个API 结合到一些代码中
在这个例子中我们想检查 app所占用的内存 是否接近内存限制 也许由于内存尖峰 捕捉到游戏的GPU追踪 因此我们可以使用内存查看器 来充分调试它 让我们开始吧 首先我们要检查app的内存占用 是否接近内存限制 如果接近 我们要捕捉下一个帧 我们只会进行正常渲染 跟不接近时一样
在这一个帧结束后 如果我们捕捉到它了 我们会停止捕捉并处理GPU追踪 由你来决定如何处理GPU追踪 潜在地 你可能还想退出游戏 或禁用这个会话的GPU捕捉 因为你可能不想在那点上 捕捉每一个帧 这会为你提供一个 利用两个API的好方式 比如进一步深究内存占用 那很棒 因此让我们来做一下吧 让我们看一下如何减少内存占用 为此 我们还有一堆最佳实践 在这部分我们要讲 不耗内存的渲染目标 资源传输 再提一下游戏资产 以及内存密集型效果 让我们从不耗内存的渲染目标开始讲 (最佳实践) 这大概就是我们在讲 内存带宽部分时的位置吧
请注意系统内存上不加载或存储 临时渲染目标 因此它们实际上不需要系统内存分配 这就是为什么你应该使用 不耗内存的存储模式的原因 特别是对于 所有多重采样的缓冲区来说 让我们看看该如何实现 再一次 只需要几行代码
在这个例子中 与给纹理描述符设置不耗内存 一样简单 并且我们要确保 渲染通道把渲染目标配置为完全临时 在这个例子中 我们把G-Buffer配置为临时 我们只需要把加载操作设置为清理 并把存储操作设置为 DontCare 因此我们没有存储G-Buffer
我们可以通过Afterpulse再看看 Digital Legends是如何实现的 在顶部我们可以看到 老版的Afterpulse 它有临时G-Buffer 但系统内存对它进行了备份
在底部 我们看到新版Afterpulse 实际上有个较大的G-Buffer
但这个G-Buffer 是完全临时的 这一次系统内存没有对它进行备份 他们对所有中间的 G-Buffer缓冲区 都使用了不耗内存存储模式 这很棒 因为新版Afterpulse 节约了大概60MB的内存占用 仅通过设置那一个标志 太棒了 并且没有做出任何妥协 这儿什么也没少 也没有任何权衡 它就是起作用了 很棒 那么现在让我们继续看 余下的最佳实践 (最佳实践) 其中一些涉及 内存和性能权衡 或内存和图片品质权衡 在这个例子中 我们要返回到资源传输
请注意 把所有资产都加载到内存中 将增加内存占用 因此你应该考虑内存和性能的权衡 并只加载全部你知道的 将被使用的资产 特别是当你接近内存限制时 并且你可能还想释放所有资源 一旦不再使用它们 潜在的比如启动画面或教程UI 都是很好的例子
这个决定很困难 幸运的是内存查看器会帮助我们处理 它是个很棒的工具 (Metal内存查看器) 我们可以使用筛选器快速查找 不用的资源 通过这样的操作会更新底部的表 然后我们可以查看在这个帧中 不使用的所有资源 在本场演讲的最后Onyechi 会在内存查看器的演示中 具体讲这个 那么现在让我们继续看 第14个最佳实践 使用较小的资产
事实上你应该只在必要时扩充资产 并再次考虑资产尺寸的图像品质 和内存权衡 确保既压缩了纹理又压缩了网格 潜在地 如果你接近了内存限制 你可能希望只加载 较小的纹理映射等级 或较低的网格LOD
但这里有一个权衡 在图像品质和内存之间 由你来决定何时采取这种操作
下一个最佳实践 第15个最佳实践与上一个很相似 我们想简化内存密集型效果
有些效果需要大的后台缓冲区 比如阴影图和SSAO 因此最佳实践是 考虑所有效果的 图像品质和内存权衡
潜在地 降低所有大的后台缓冲区的分辨率 并且如果你接近内存限制 就同时禁用全部效果
这要做出权衡 并且你需要意识到这个问题 但有时候我们没有其它选择
我们要将的下一个最佳实践 稍微有点不一样 在这个最后一部分中 我要介绍一些更先进的概念 从而帮助我们进一步减少内存占用 我们会讲到Metal资源堆积
可清除的内存以及管线状态对象
让我们从Metal资源堆积开始讲 (Metal资源堆积) Metal资源堆积将允许app 明确控制在前台发生的 大的内存分配 在这个例子中 我们要把三个单独的纹理 它们分别有自己的分配 放到一个Metal资源堆积中 它是一个分配 那会分别包含这三个纹理 这还将允许系统把那些纹理 打包到一起 从而节约一些内存 (最佳实践) 但最节约内存的是使用别名 请注意渲染帧可能需要 大量的中间内存 特别是你的游戏向内扩展后处理管线 因此针对那些效果使用 Metal资源堆积 并尽可能地使用别名非常重要 比如你可能想针对 所有没有依存关系的资源 重新使用内存 潜在地 比如在SSAO 和景深效果中找到的那些
现在让我们看看那看起来如何 在这个例子中 我们拥有 跟以前一样的Metal资源堆积 但如果没有同时使用那三个纹理 我们可以潜在地给它们取个别名 通过这样做 我们会节约大量内存 这真的会帮助你的游戏 形成更复杂的后处理管线 而不需要为所有中间的渲染目标 和所有中间的内存付出巨大的代价 因此它是个很棒的功能 你应该考虑权衡 (Metal资源堆积) 现在让我们谈谈另一个先进概念 即可清除的内存 (可清除的内存) 可清除的内存有三个状态: 非易失性、易失性和空 请注意易失性和空分配 不计入app的内存占用 那是因为系统 可以在某个时刻回收那个内存 如果是易失性情况的话 或者之前已经回收它了 如果是空的情况的话 因此你可能需要重新生成那些资源
但使得这种内存非常合适资源缓存
因此最佳实践 第17个范例就是 把资源标记为易失性资源 易失性资源可能会占用 游戏的内存占用的大部分
并且Metal允许你 明确管理那些资源 明确设置所有资源的可清除状态 因此你要注意缓存 特别是占用绝大部分闲置内存的 那些缓存 并谨慎管理它们的可清除状态 从而使它们不计入 游戏的内存占用之内 让我们来看一段很短的代码 那会给你提供一个如何实现的例示例 在这个例子中 我们有纹理缓存 也可以是缓冲区缓存
我们要把那个缓存中 所有纹理的可清除状态 设置为易失性 因为我们知道 那个缓存多数时候是限制状态 我们偶尔只使用一个纹理 但不是太经常
因此如果我们需要使用 那个缓存中的资源 我们需要把它标记为非易失性 那将确保系统不会移除它的备份数据
在这个例子中 它是空 就是我们刚才讲到过的一个状态 我们实际上可能需要重新生成数据 可能不需要生成 取决于你所管理的缓存类型 但在我们这样做之后 我们就可以正常使用资源 就和不为空时一样
还有一个很好的范例就是 查看公共缓冲区的完成情况 并当公共缓冲区完成后就着手处理 潜在地 再一次把那个资源标记为易失性 因此它不会计入你的内存占用内 你可以非常明确地执行 并且事实上 你应该很有侵略性 特别是当你有大量多数时候 是闲置内存的缓存时
那么让我们介绍最后一个概念 即管线状态对象 (管线状态对象(PSO)) 大多数人已经很熟悉那些了 PSO封装大部分 Metal渲染状态 它们由描述符构建 包含顶点函数和片段函数 以及其它状态 比如混合状态 和顶点描述符 所有这些都被编译到 最终的Metal PSO中
我们只需要用这个最终的 Metal PSO进行渲染 因此下一个最佳实践是 明确地对它们进行权衡 因为Metal允许你的app 提前加载大多数渲染状态 你也应该这样做 那会很大地提高性能 然后 如果你那样做 就要考虑内存权衡 如果你的内存有限 一定不要抓住那些 不再需要的PSO引用不放 并且这一点也很重要 不要在你创建PSO缓存之后 还抓住Metal函数引用不放 因为渲染不再需要它们了 只有创建新PSO需要它们
让我们通过再看一下描述符 来解释一下 这是管线状态对象 以及管线状态对象描述符 并且这个最佳实践就是 在你创建PSO之后 释放顶点函数和片段函数的引用 潜在地 仅抓住那些当在加载时 填充主PSO缓存的那些引用 然后也要考虑释放PSO自身 当你的内存接近内存限制时 如果你知道不再需要那个PSO的话
现在请欢迎Onyechi上台来 做一个关于内存查看器的演示 (演示)
谢谢Guillem 大家好
你们已经从之前的幻灯片中 大概了解了 内存查看器 现在我要给你们演示 如何使用它了解内存占用 这是为了获得更好的内存性能 而进行的优化
在这里我们再一次捕获 来自Afterpulse早期版本 的这个帧 然后左上方有调试导航器 你可以找到一个新的内存计 当我点击它时 它会把我带入内存查看器中
这给我们显示了所捕捉的帧的 所有活跃的Metal资源 我的目标是找机会减少它的内存占用 因此开始 让我们先看看图表中的橘色条 这些显示了资源是如何 按类型进行分配的 我们可以看到纹理构成了最大的部分 对吗? 大概有440MB 现在我想关注这些部分 我可以通过点击筛选按钮实现
现在图标和表都已经更新了 只显示纹理
那么接下来 Guillem提到了 当查看如何减少内存占用时 要从未使用的资源开始 那是个不错的地方 因此 让我们实现它吧 这一次让我们看一下蓝色条
它代表我们的使用 并且我们看到我们有大约200MB 那么是不用的纹理 因此作为提醒 不使用的资源是指不会对这个 所渲染的帧的最终输出 做出贡献的资源 那意味着GPU不能访问它们
好的 那么我可以再一次 通过点击不用的筛选器实现 我们现在正在看的就是不用的纹理
接下来我会按所分配的尺寸 对表进行分类 从而我们只关注最大的纹理
并且我们可以立刻看到最大的纹理 是大约13MB 有一个问题 这里的问题与你在之前的演示中的 依存查看器中的问题差不多 让我们点击它来查看它是关于什么的
好的 那么它说我们有一个 大的不用的纹理 CPU不对它进行访问 并且从未被绑定到命令编码器中 以避免加载资源 或把它变为易失性资源 我们还可以有另一种解决方式 就是查看属性、 CPU访问和自上次绑定之后的时间 我们可以明显看到这部分绝对没有 被CPU或GPU访问过 因此我们可以自信地说 这是一个应该被释放的资源 除非我们实际需要它
好的 这非常鼓舞人心 我们会非常迅速的节约 13MB的内存 让我们查看下一个问题
在这个例子中 纹理被识别为 临时资源 CPU不对它进行访问 并且在过去的47秒内 它从未被绑定到命令编码器上 因此换句话说 这个纹理在过去的47秒内 没有使用这个帧或任意其它帧 因此这是一个 应该被标记为易失性的资源 如果可以的话 因为它只是偶尔对帧做贡献 好的 这非常好
我们非常迅速地找回了 14MB的内存
的确 当我们要减少内存占用时 查看不用的资源是个良好的开端 并且问题会帮助你快速识别 最需要移除的资源 但你还应该注意一下属性 自上次启动之后的时间 来了解哪个不用的资源 从未被提交给GPU 即使CPU可能会对它们进行访问
好的 让我们换个话题吧 看看我们可以发现哪些问题 或节约哪些内存 当纹理在使用中时
好的 这一次我要切换到 使用的筛选器 从而我们可以查看使用的纹理 我们可以看到其中一个最大的纹理 大约是18MB 有两个问题 让我们来看看分别是什么
好的 第一个是关于无损压缩 纹理选择退出无损压缩 由于ShaderWrite 使用标记的存在 即使它被唯一地用作渲染目标
第二个问题是关于存储模式
我们的纹理 即渲染目标 被识别为临时的纹理 很明显意味着针对这个纹理 没有任何加载或存储操作 但很遗憾 它有共享的存储模式 当它真的应该是不耗内存的时候
我们在这里有两个不同的推荐 如果你思考一下 我们只需要从中选一个就好 但在这一点上 我应该强调这两个推荐 都由内存查看器提供 都是基于把数据收集到当前帧中 然而 你更清楚 你打算如何在当前帧之外 使用你的资源 因此 如果我们确定这个纹理 将保持临时状态 比如将来的渲染通道 然后切换到不耗内存状态 这是更好选项 这样会减少纹理尺寸的内存占用 即18MB 如果 在另一方面 我们的纹理在将来的渲染通道中 不是临时状态了 那么我们应该认真考虑 选择不耗内存压缩 因为那样会对我们的内存带宽 产生积极影响 刚才Guillem曾建议过 在这个例子中 移除冗余的 ShaderWrite标记 就可以了
那么
我们只是披露了用内存查看器 可能实现的一部分功能 你已经了解如何使用它 仅通过几次鼠标点击 它可以让你轻松地 了解你的内存使用情况 同时它还可以让你快速识别 那些可能会影响你的性能的 很难发现的问题 然后我要欢迎Guillem 回到舞台上来 谢谢
谢谢Onyechi 那是一个很棒的演示 (演示) 很酷 那么我们今天讲了很多最佳实践 我们… 总共讲了18个 内容真的非常多 请注意大部分最佳实践 实际上都很有关联 对吧? 绝大部分内存带宽最佳实践 还会帮助你减少内存占用 因此… 也许思考所有这些内容的最佳方式 是这个针对你的Metal游戏 或app的优化清单 当你认真思考每一个元素时 你一定会提交 优化的Metal游戏和app 这就是本场演讲所讲的内容
要获取更多信息 请查看我们的在线文档 并在今天下午3点参加我们的演讲 非常感谢 我希望你们享受接下来的会议 谢谢
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。