MAPS.ME上的Apple Metal

图片 大家好!

在世界上,OpenGL上有大量的应用程序,而且苹果似乎不太同意这一点。 从iOS 12和MacOS Mojave开始,不推荐使用OpenGL。 我们已将Apple Metal集成到MAPS.ME中,并准备分享我们的经验和成果。 我们将告诉您图形引擎的重构方式,必须面对的困难以及最重要的是我们现在有多少FPS。

欢迎所有有兴趣或正在考虑将Apple Metal支持添加到图形引擎的人。

发行


我们的图形引擎被设计为跨平台的,并且由于OpenGL实际上是我们感兴趣的平台集(iOS,Android,MacOS和Linux)的唯一跨平台图形API,因此我们选择它作为基础。 我们没有进行额外的抽象级别来隐藏OpenGL特有的功能,但幸运的是,它保留了实现它的潜力。

随着新一代图形API Apple Metal和Vulkan的出现,我们当然考虑了它们在我们的应用程序中出现的可能性,但是,我们被以下方面所阻止:

  1. Vulkan只能在Android和Linux上运行,Apple Metal只能在iOS和MacOS上运行。 我们不想失去图形API级别的跨平台性,这会使开发和调试过程变得复杂,会增加工作量。
  2. Apple Metal上的应用程序无法在iOS模拟器上构建和运行(顺便说一下,直到现在),这也会使我们的开发复杂化,并且无法完全摆脱OpenGL。
  3. 我们用于创建内部工具的Qt框架仅支持OpenGL( 现已支持Vulkan )。
  4. Apple Metal没有也没有C ++ API,这迫使我们不仅要为运行时提供抽象,而且要为应用程序的构建阶段提供抽象,当时部分引擎是用Objective-C ++编译的,而另一部分则是更大的C ++。
  5. 我们还没有准备专门为iOS创建单独的引擎或单独的代码分支。
  6. 在一名图形开发人员的工作中,至少对实施情况进行了六个月的评估。

当苹果在2018年春季宣布将OpenGL移至已弃用状态时,很明显不再可能推迟,并且上述问题需要以一种或另一种方式解决。 此外,我们长期以来一直致力于优化应用程序速度和功耗,Apple Metal似乎能够提供帮助。

决策选择


几乎立即我们注意到了MoltenVK 。 该框架使用Apple Metal模拟Vulkan API,并且其源代码最近已打开。 使用MoltenVK似乎可以用Vulkan替换OpenGL,而根本不必处理Apple Metal的单独集成。 另外,Qt开发人员拒绝了单独支持在Apple Metal上进行渲染的支持,而支持MoltenVK。 但是,我们被阻止了:

  • 需要支持无法使用Vulkan的Android设备;
  • 无法在没有OpenGL的后备状态的情况下在iOS模拟器上启动;
  • 由于MoltenVK从SPIR-V或GLSL源代码为Apple Metal生成实时着色器,因此无法使用Apple工具来调试,分析和预编译着色器;
  • 发布新版本的Metal时,需要等待MoltenVK的更新和错误修复;
  • 不可能针对Metal进行微妙的优化,而对于Vulkan则不是特定的或不存在的。

原来,我们需要保存OpenGL,这意味着我们不能不从图形API中提取引擎。 Apple Metal,OpenGL ES和未来的Vulkan将用于创建图形引擎的独立内部组件,这些组件可以完全互换。 当Metal或Vulkan由于某种原因不可用时,OpenGL将充当后备选项的角色。

实施计划如下:

  1. 图形引擎重构以抽象所使用的图形API。
  2. 渲染到Apple Metal以获取iOS版本的应用程序。
  3. 为渲染速度和功耗制定适当的基准,以查看现代的较低级别的图形API是否可以使该产品受益。

OpenGL和Metal之间的主要区别


为了了解如何抽象图形API,我们首先确定OpenGL和Metal之间的主要概念差异是什么。

  1. 并非没有理由相信,Metal是较低级别的API。 但是,这并不意味着您必须自己编写汇编程序或实现光栅化。 从金属执行少量隐式动作的意义上来说,Metal可以称为低级API,也就是说,几乎所有动作都必须写给程序员自己。 OpenGL隐式地做很多事情,从支持对OpenGL上下文的隐式引用开始,然后将此上下文链接到创建它的流。
  2. 在Metal中,“否”实时验证团队。 在调试模式下,当然存在验证,并且比许多其他API都要好得多,这主要是由于它与Xcode紧密集成。 但是,当程序发送给用户时,就不再有任何验证,程序只会在第一个错误时崩溃。 不用说,OpenGL仅在最极端的情况下才会崩溃。 最常见的做法:忽略错误并继续工作。
  3. Metal可以预编译着色器并根据它们构建库。 在OpenGL中,着色器是在程序处理过程中从源代码编译的,因为这负责在特定设备上进行OpenGL的特定低级实现。 着色器编译器实现上的差异和/或错误有时会导致异常错误,尤其是在中国品牌的Android设备上。
  4. OpenGL积极使用状态机,该状态机几乎为每个功能都增加了副作用。 因此,OpenGL函数不是纯函数,并且顺序和调用历史通常很重要。 金属不会隐式使用状态,并且不会将其保留的时间超过渲染所需的时间。 状态作为预先创建和失败的对象存在。

图形引擎重构和嵌入Metal


重构图形引擎的过程基本上包括找到最佳解决方案,以摆脱我们引擎一直在积极使用的OpenGL功能。 从其中一个阶段开始,嵌入金属并行进行。

  • 如前所述,OpenGL API具有一个称为上下文的隐式实体。 上下文与特定线程相关联,并且在该线程中调用的OpenGL函数本身会找到并使用此上下文。 Metal,Vulkan(是的,以及其他API,例如Direct3D)不能以这种方式工作,它们具有称为设备或实例的类似显式对象。 用户自己创建这些对象,并负责将它们传输到不同的子系统。 通过这些对象,可以对图形命令进行所有调用。

    我们将抽象对象称为图形上下文,在OpenGL中,它简单地装饰了OpenGL命令的调用;在Metal中,它包含根MTLDevice接口,通过该接口调用Metal命令。

    当然,我必须在所有子系统中分布该对象(并且由于我们具有多线程渲染,因此甚至还要分布几个这样的对象)。

    我们将命令,编码器及其管理的队列隐藏在图形上下文中,以免散布在OpenGL中根本不存在的实体。
  • 用户设备上的图形命令验证消失的前景使我们不满意。 我们的质量检查部门无法全面涵盖各种设备和操作系统版本。 因此,我们不得不在以前从图形API收到有意义错误的地方添加详细日志。 当然,此验证仅添加到图形引擎的潜在危险和关键位置,因为使用诊断代码覆盖整个引擎实际上是不可能的,并且通常对性能有害。 新的现实是,至少在渲染方面,现在已经过去使用日志进行用户测试和调试。
  • 我们以前的着色器系统不适合重构;我必须完全重写它。 这里的重点不仅在于着色器的预编译及其在项目组装阶段的验证。 OpenGL使用所谓的统一变量将参数传递给着色器。 结构化数据传输仅在OpenGL ES 3.0中可用,并且由于我们仍然支持OpenGL ES 2.0,因此我们根本没有使用此方法。 Metal使我们使用数据结构传递参数,而对于OpenGL,我们不得不提出将结构字段映射到统一变量的想法。 另外,我不得不用“金属着色”语言重新编写每个着色器。
  • 当使用状态对象时,我们不得不花招。 通常,在OpenGL中,所有状态都应在渲染之前立即设置,在Metal中,这应该是先前创建并验证的对象。 显然,我们的引擎使用了OpenGL方法,并且通过初步创建状态对象进行重构与完全重写引擎相当。 为了剪切该节点,我们在图形上下文内创建了一个状态缓存。 第一次生成状态参数的唯一组合时,会在Metal中创建一个状态对象并将其放置在缓存中。 对于第二次及以后的时间,仅从高速缓存中检索对象。 这在我们的地图中有效,因为状态参数的不同组合的数量不会太大(大约20-30)。 对于复杂的游戏图形引擎,此方法几乎不合适。

结果,经过大约5个月的工作,我们能够首次在Apple Metal上全面渲染启动MAPS.ME。 现在是时候找出发生了什么。

渲染速度测试


实验技术


我们在实验中使用了不同版本的Apple设备。 所有这些都已更新到iOS12。对所有用户都执行了相同的用户脚本-地图导航(移动和缩放)。 该脚本的脚本可确保每次在每台设备上启动时几乎都能完全识别应用程序中的进程。 作为测试地点,我们选择了洛杉矶地区-MAPS.ME中负载最重的地区之一。

首先,该脚本在OpenGL ES 3.0上渲染时运行,然后在同一台设备上在Apple Metal上渲染。 在两次启动之间,应用程序已从内存中完全卸载。
测量了以下指标:

  • 整个帧的FPS(每秒帧数);
  • 帧的仅用于渲染的部分的FPS,不包括数据准备和其他逐帧操作;
  • 慢帧的百分比(大于30毫秒),即 人眼可以感觉到的那种混蛋。

在测量FPS时,不包括直接在设备屏幕上绘图,因为与屏幕刷新率的垂直同步无法获得可靠的结果。 因此,框架被绘制到内存中的纹理中。 为了同步CPU和GPU,OpenGL使用了额外的调用glFinish ,而Apple Metal使用了waitUntilCompleted作为MTLFrameCommandBuffer

iPhone 6siPhone 7+iPhone 8
Opengl金属制品Opengl金属制品Opengl金属制品
Fps106160159221196298
FPS(仅渲染)157596247597271833
慢帧的分数(<30 fps)4.13%1.25%5.45%0.76%1.5%0.29%

iPhone XiPad Pro 12.9'
Opengl金属制品Opengl金属制品
Fps145210104137
FPS(仅渲染)248705147463
慢帧的分数(<30 fps)0.15%0.15%17.52%4.46%

iPhone 6siPhone 7+iPhone 8iPhone XiPad Pro 12.9'
在金属上加速框架(N次)1,51.391,521.451.32
在金属上加速渲染(N次)3.782.413.072.843.15
改善慢帧(N次)3.37.175.171个3.93

结果分析


平均而言,使用Apple Metal的帧性能提升为43%。 iPad Pro 12.9'的最小值固定为-32%,iPhone 8的最大值固定为52%。有一个依存关系:屏幕分辨率越低,Apple Metal越能超过OpenGL ES 3.0。

如果我们评估框架中直接负责渲染的部分,则Apple Metal的平均渲染速度提高了3倍。 这表明组织结构要好得多,因此与OpenGL ES 3.0相比,Apple Metal API的效率更高。

Apple Metal上的慢帧(超过30毫秒)的数量减少了约4倍。 这意味着动画和在地图上移动的感觉变得更加流畅。 最糟糕的结果记录在iPad Pro 12.9'上,分辨率为2732 x 2048像素:OpenGL ES 3.0的慢帧约为17.5%,而Apple Metal的仅为4.5%。

功率测试


实验技术


在iOS 12的iPhone 8上测试了功耗。执行了相同的用户场景-地图导航(移动和缩放)持续1小时。 编写脚本的脚本可以确保每次启动时应用程序中的进程几乎完全相同。 洛杉矶地区也被选为测试地点。

我们使用以下方法测量能耗。 设备未连接到充电。 在开发者的设置中,电源记录已启用。 开始实验之前,设备已充满电。 实验在脚本结尾处结束。 在实验结束时,记录了电池的充电状态,并将功耗日志导入实用程序中,以Xcode对电池进行性能分析。 我们记录了在GPU上花费了多少费用。 另外,这里我们还通过包括地铁方案的显示和全屏抗锯齿功能对渲染进行了加权。

屏幕亮度在所有情况下都不会改变。 除system和MAPS.ME外,没有其他进程被执行。 飞行模式已打开,Wi-Fi和GPS已关闭。 另外,进行了几次对照测量。

结果,对于每个指标,形成了Metal与OpenGL的比较,然后对比率进行平均以获得一个汇总估计。

Opengl金属制品增益
电池电量耗尽32%28%12.5%
在Xcode中分析电池使用情况1.95%1.83%6.16%

结果分析


平均而言,Apple Metal上渲染版本的功耗略有改善。 我们的GPU应用程序的功耗不会受到很大的影响,约为2%,因为MAPS.ME在使用GPU方面不能被称为高负载。 在为CPU上的GPU准备指令时,可能会通过减少计算成本来实现小幅增长,​​但不幸的是,借助分析工具无法区分这些指令。

总结


嵌入金属花费了我们5个月的开发时间。 但是,有两个开发人员几乎总是这样做。 显然,我们在渲染性能方面取得了显著成绩,在能源消耗方面获得了一点收获。 此外,我们有机会以更少的精力来嵌入新的图形API,尤其是Vulkan。 结果,几乎完全“整理”了图形引擎,因此,我们发现并修复了一些旧的错误和性能问题。

对于我们的项目是否真的需要在Apple Metal上渲染的问题,我们准备肯定地回答。 我们热爱创新,或者苹果最终可以放弃OpenGL并不是什么重要的事情。 这只是2018年,而OpenGL出现在遥远的1997年,是时候采取下一步了。

PS直到我们在所有iOS设备上启动该功能为止。 要手动启用它,请在搜索栏中输入?metal并重新启动应用程序。 要将渲染返回到OpenGL,请输入?gl命令并重新启动应用程序。

PPS MAPS.ME是一个开源项目。 您可以在github上阅读源代码。

Source: https://habr.com/ru/post/zh-CN430850/


All Articles