作者:米广
部门:业务技术/iOS组
前言
WWDC21中发布的macOS Monterey中新增了可变刷新率的Adaptive-Sync显示技术,自此行业通用的可变帧率技术登录Mac生态;今天我们就围绕苹果生态中的两种可变帧率显示技术,讨论如何为用户呈现最佳体验;本文中首先我们会介绍一下macOS中的Adaptive-Sync技术;这项技术为macOS的全屏显示的App和游戏提供了更加灵活的帧率,更加流畅体验,基于此深入讨论有关顺滑渲染的最佳实践;然后我们会了解现有的iPad Pro和iPhone 13 Pro上的ProMotion技术,并进一步探讨能在不同帧率下基于CADisplayLink的最佳技术实践,在自定义绘图时为用户带来流畅的体验;本篇文章是基于Session10147 - Symbolication: Beyond the basics撰写,该Session的演讲者是来自Apple GPU软件团队的WindowServer工程师Kyle Sanner和CoreAnimation工程师 Alex Li。
1、基于macOS 平台中的Adaptive-Sync技术为用户提供顺滑体验
Adaptive-Sync 技术简介
Adaptive-Sync是由美国的非营利性标准制定组织VESA提出的基于DisplayPort接口的可变帧率显示技术,目的是为了解决画面撕裂和卡顿掉帧的问题;我们平时常用的DisplayPort接口就是VESA制定的标准接口;该组织不同于索尼等盈利性商业公司, DisplayPort相较于索尼的HDMI是一种免费而开放的技术,而HDMI受专利保护且是盈利性收费视频传输界面,当然HDMI有完善的数字内容保护体系HDCP;Adaptive-Sync类似于现AMD的Free-Sync和NVIDIA的G-Sync的动态帧率技术,但该技术免费且开放;因此在不同品牌的显示器和不同品牌的显卡中,只要都支持Adaptive-Sync,那就支持动态帧率技术,不受制于AMD显卡必须买AMD认证支持的Free-Sync显示器等问题;免费开放技术将利于市场广泛推广应用可变帧速率显示生态;苹果已经明确在macOS中将提供基于Adaptive-Syn的可变帧率显示技术支持。
首先,我们来回顾一下 Apple 平台中的屏幕类型~
Apple 生态中的大部分显示器都是固定帧率的,也就是屏幕只要被点亮,就会以每秒固定的刷新频率进行刷新与显示;但iPad Pro和最新发布的iPhone13Pro系列的ProMotion和最近发布的Mac和MacBook Pro 2021中的Adaptive-Sync和ProMotion是个例外;首先,我们来了解一下Mac中的Adaptive-Sync新技术。
固定与可变帧率的区别
在讲解可变帧率的屏幕刷新技术前,我们先回顾一下固定帧率的显示技术;如下图所示,在60Hz的显示器中,帧与帧间的刷新间隔是固定的16毫秒;如果在帧缓存流里准备好了新的一帧,新的一帧就会被呈现出来;如果没有准备好新的帧,那么前一帧就会被继续显示;当固定帧率提高到120Hz时,我们提高了一倍帧刷新率,这导致每一帧的准备时间缩小了一倍到8毫秒;但固定帧率的显示是相似的,只是刷新速度的快慢有区别,也就是帧准备时间长短有别。
Adaptive-Sync 可变帧率带来的变化和优势
在Adaptive-Sync显示中,每一帧都有一个可变的时间窗口,这个时间窗口替代了原有的固定的帧刷新时间间隔;这个间隔取决于具体连接的可变帧率显示器的帧率支持范围;下面以可变帧率40-120Hz为例,这意味着每一帧可以在屏幕中展示8-25毫秒;但需注意,一旦一个帧的展示时间超过了最大的25毫秒的极值,系统就会强制刷新帧,刷新期间会有短暂的不可用时间。
我们对比一下,就可以发现可变帧率带来的好处;在120Hz固定帧率的屏幕中,如果App能够在8毫秒内完成帧绘制,这将给用户带来一个顺滑的120Hz体验,但假设由于场景复杂度提升,某些帧的绘制时间超过了8毫秒,需要9毫秒才能在帧缓存中准备完毕,这会导致前一帧显示16毫秒(重新被展示一次),而不是8毫秒;这种实际耗时16毫秒的帧,会导致呈现给用户的实际显示效果,有明显可察觉的卡顿。
在可变帧率的显示器中,您可以设置帧在绘制完成后立刻呈现至屏幕,而无需在固定时间节点提交呈现帧;因此如果当前帧的绘制用时为9毫秒,那么在绘制完成时就可以主动提交帧显示,这其中1毫秒的延迟,不会导致易被用户察觉的卡顿;在这种工作模式下,针对复杂帧的绘制,可以通过拆分复杂帧绘制任务,以及适当增加帧绘制时间,来提供一个平滑、均匀的帧绘制。
基于真实场景的 Adaptive-Sync 的最佳实践
需要可变帧率的场景
设想这种情况:一个可能运行复杂场景的游戏,基本可以稳定在90Hz的刷新速率,但特定复杂场景会导致帧速率下降至66Hz;通过实时监测GPU的工作负荷,开发者可以有意在复杂场景中降低画面质量或适当增加帧绘制时间,直至画面场景复杂度恢复至平均水平;如此操作,可以为用户提供一种较为顺滑的帧呈现。
基于此,我们可以发现固定帧率和动态帧率的最佳实践的不同;在固定帧率的机制中,如果帧绘制时间超过现有显示器帧率的固定时间时,我们会建议将所有帧绘制的时间都延长,也就是使用更低的阵刷新速率,以使所有帧绘制都能够在刷新间隔中在GPU上完成;下图就展示了在一个固定60Hz帧率显示器中,42Hz刷新率时部分帧会无法呈现或被跳过,因此只能降级到30Hz进行帧绘制。
而在可变刷新帧率机制中,我们会建议App在任何情况下都应该尽力提供更高的帧刷新速率,App需要平衡GPU负载和刷新率之间的平衡,最大的帧渲染时长不能超过最低动态帧率的间隔,否则会导致剧烈可察觉的卡顿。因此,您无需担心现有显示器所支持的可用帧率是固定的组合,您可以竭尽所能提交顺滑绘制,也就是尽量均等的安排帧绘制耗时,而动态调整输出的帧步调(帧率),在Adaptive-Sync可变帧率环境中,在可支持范围内的帧率都可以被正常呈现,如48Hz与110Hz。
启用 Adaptive-Sync
基于此,我相信您已经对可变帧率有了进一步的理解;我们来谈谈如何在游戏中启用Adaptive-Sync可变帧刷新率技术。
首先需要软硬件的支持,包括M1架构的Mac和近年来部分Intel架构的Mac;其次需要Adaptive-Sync支持的显示器,并开启显示器的Adaptive-Sync功能;显示器与Mac通过 DisplayPort界面连接;其次游戏和App必须以全屏方式运行。
接下来我们深入API来进一步了解Adaptive-Sync首先您需要获取当前环境是否支持可变刷新帧率,对此您可以通过 NSScreen的新属性来判断;在支持可变刷新帧率的环境中,这两个值会反应最大和最小帧率所对应的刷新时间间隔;而在不支持可变帧率的环境中,这两个值会是相等的数值;同时需要判断当前App是否在全屏模式中运行;最后通过上述两个条件,确保Adaptive-Sync已经正常开启。
在绘制中控制帧步调
基于Metal绘制技术提供的API,动态调整帧绘制的步调,以在自适应同步显示器上流畅显示。您可以直接使用我们的MetalDrawable相关API ,这些API已经内置了帧步调技,例如presentAfterMinimumDuration或者 presentAtTime;当然您也可以基于present now逻辑自己实现呈现逻辑和自定义计时器管理。
我们来结合一个简单的例子来看看与之前固定帧率显示器相比,我们需要为App做出哪些调整;在这个例子中,我们会获取一个Drawable实例,设置好GPU的工作,完成后呈现在屏幕上;我们需要依赖于GPU完成接下来 Drawable渲染工作的压力来设置帧速率。在固定帧率显示器上,我们很容易知道这不是最好的实践逻辑,因为我们无法保证GPU的渲染频率与显示器帧速率保持一致;但基于Adaptive-Sync显示器和动态帧步调,同样是之前这个场景,我们可以在Instruments中观察到个别帧消耗时间有所增加,这似乎没有问题;但这个例子中的实际问题是,特定场景的单独帧,会周期的被显示,这个复杂场景帧会消耗更多的时间,会形成用户可能感知的卡顿,在下图中红色部分所示:
首先让我们尝试以较大的固定平均的渲染速率来解决这个问题;当然在用户可以自定义帧率的情形中,也可以使用该方法;我们尝试固定帧率为78Hz,并将该间隔值传入afterMinimumDuration中。
实际效果我们将得到一个帧率低但渲染均匀顺滑的效果,同时整个App占用了更少的CPU和GPU资源消耗;这种方法可以解决用户遇到的卡顿,但实际会导致整个App的体验降低(由于强制降低了整体的帧率)。
基于 GPU 负荷动态平滑实际绘制帧率
而在Adaptive-Sync显示器中,我们可以尝试通过GPU负载(前述帧渲染所需时间)动态计算,而非直接赋予固定值的方式来设定帧速率;具体算法如下图所示,我们通过计算前述帧的滚动平均值,来动态预估下一帧所用时间,结合Adaptive-Sync ,动态赋予下一帧所需预估的时间;这里的初始值设为显示器最高帧速率时的可用绘制时间。
最终我们可以充分利用GPU资源为用户呈现尽可能高帧率的画面;且在不同性能设备,不同的GPU负载下,都无需进行额外的代码更改,如下图所示,性能优秀与过时的Mac都有一个均匀的帧呈现速率。
综上,我们了解了几个新API,以及根据GPU负载的动态帧速率控制思路,让我们能够利用Adaptive-Sync技术为用户提供更顺滑的渲染体验;这篇文章编写时,还未发布MacBook Pro 2021,目前MacBook Pro 2021的内建屏幕支持了ProMotion显示,实际上外接显示器和内部ProMotion显示器的驱动方法有所区别,具体区别苹果官方目前也未说明。目前除了预览版Safari支持120Hz,其他任何Mac App都暂时不能以120Hz驱动这块内建屏幕,有关更多信息可以关注苹果的API与进一步文档更新,接下来让我们继续了解一下iPad与iPhone 13 Pro中的可变刷新率技术。
2、基于 iPad Pro 与 iPhone 13 Pro 平台中的 ProMotion 技术为用户提供顺滑显示体验
ProMotion 可变帧率技术简介
接下来我们来讨论iPad Pro与iPhone 13 Pro中的ProMotion可变帧率技术;ProMotion自iPad Pro 2017年发布以来,可以提供24Hz - 120Hz的刷新率;最近发布的iPhone 13 Pro可提供10Hz - 120Hz的刷新率,iPadOS 15和iOS 15中,省电模式会将ProMotion刷新率降低至60Hz ,也就是120Hz的刷新率并不总是可用;因此作为开发者,需要处理和协调帧的绘制步调以在ProMotion因种种原因而帧率下降时,仍为用户提供正确流畅的渲染内容,接下来我们将讨论 。
正如之前提到的60Hz的显示器16毫秒刷新一次,保持固定的刷新节奏,当屏幕限制30Hz、20Hz的内容是,显示器本身仍旧保持60Hz的刷新率,因此相同帧会被重复展示,这种不可察觉的刷新操作会影响电池使用时长。
而在ProMotion技术显示器上,帧刷新速率最高为120Hz ,iPad Pro最低24Hz iPhone 13 Pro最低为10Hz,ProMotion在不同刷新频率下,不会重复刷新之前帧,而是根据当下的帧速率动态刷新帧,因此刷新间隔从8毫秒到99毫秒不等;动态的帧率刷新可以节约电池使用时长;请注意ProMotion显示器与Adaptive-Sync显示器有所区别,ProMotion显示器无法支持基于区间的可变帧速率,而Adaptive-Sync显示器支持,例如您可以在Adaptive-Sync 中以110Hz、78Hz、49Hz任意在支持区间内的帧速率来呈现帧,但在ProMotion中只有特定的120Hz、60Hz、40Hz、24Hz、10Hz等特定支持的帧率,后文中将会提到这点。
ProMotion `120Hz` 的可用性限制
ProMotion 的120Hz并不总是可用帧速率,用户可以在辅助功能设置中打开限制帧速率动态变换,将最大帧率限制至60Hz;当设备负载过大,出现过热情况时,系统会限制120Hz的可用性;在iPadOS 15和iOS 15中,低电量模式的的设备会强制限制ProMotion最大帧率为60Hz。对于上述情况,绝大多数App都无需特别适配。但如果您的App执行逐帧的自定义绘制,那么您需要处理上述帧速率限制事件。
ProMotion 绘制的最佳实践
CADisplayLink 与动态帧率
首先DisplayLink有基于CoreAnimation的CADisplayLink和基于CoreVideo的CVDisplayLink,前者在除macOS之外的系统中可用,后者在macOS中可用;因为iPad与iPhone 13 Pro中的ProMotion基于iPadOS和iOS ,因而这里我们只讨论CADisplayLink;DisplayLink的vsync callback事件可以理解为与屏幕帧刷新速率稳定同步的一个计时器回调;在设备的帧速率发生变化时,与CADisplayLink的时序回调保持一致的帧渲染步调,是保证App顺滑体验的关键;虽然DisplayLink可以视为一个计时器,但我们不能自己新建NSTimer计时器来实现此逻辑,因为自己新建的NSTimer的步调不可能和可能变化的帧速率同步;通过给CADisplayLink的preferredFramesPerSecond赋值,可以调节vsync的回调间隔贴近于您所期望的值;也可以通过timestamp和targetTimestamp来获取上一帧和下一帧的渲染时长等上下文信息,必要时可以像前述提到的Adaptive-Sync的动态帧率计算逻辑,来实现一个基于当前环境的最大帧率;请注意在iOS设备中,只有自定义的CALayer渲染内容,以及Metal API的内容需要自己控制ProMotion的显示帧率,其他框架和 PI目前苹果已经完成内部实现更新,自动支持ProMotion, 具体支持情况如下:
绘制步调的最佳实践
在这里我们并不直接讨论如何实现自定义动画和渲染循环,但我们提供4个最佳实践,帮助您自定义绘图步调尽量与__vsync__回调的时间保持一致,以及避免一些常犯的错误。
1、获取当前硬件帧速率等信息
您可以从UIScreen获取硬件支持最大帧速率,在ProMotion显示器中,这个值永远为120Hz,即使有前述的降低帧率的事件发生,这里仍然为。20Hz。
2、获取CADisplayLink的实际帧速率
请注意在ProMotion中,您可以通过给setPreferredFramesPerSecond赋值,来声明您想要的帧率,但由于ProMotion支持的帧率是有限的120Hz、60Hz、40Hz、30Hz、24Hz、10Hz(10Hz只有iPhone支持),因此申请在这些既定帧率之外的帧率(如68Hz),系统会自动选择一个与您声明帧率就近的支持的帧率来显示60Hz);具体真实帧率逻辑会类似下图中的场景。
3、 使用targetTimestamp来规划绘图所用时间
下面的例子中, 当CADisplayLink的实际帧速率发生变化时,targetTimeStamp相较于timestamp属性与实际变化更同步,基于此步调渲染和提交显示的帧,更为顺滑,而不会有明显可感知的卡顿。
因此,在ProMotion中,尽量使用targetTimestamp而不是timestamp来规划帧绘制时间和提交节奏;在实际使用中,您可以使用targetTimestamp属性直接替换现有代码中所有的timestamp属性;就是这么简单。
4、动态计算合适的帧速率
targetTimestamp与imestamp之间的差值反映了预估的vsync callback之间的时间,但实际时间可能有所差异;比如CPU和GPU被其他高优先级任务所调度,或是runloop在忙于一些其他事,这些情况下,vsync callback回调可能被完全略过(回调丢失);因此保持正确的自定义绘图步调是提供顺滑用户体验的关键。
下面的例子包含了CADisplayLink回调延时与回调跳过两种情况
一般而言针对回调延时,会采取舍弃一帧的策略;回调跳过发生时,则一般采取舍弃一帧并提前绘制下一帧的策略;现在假设这一帧的绘制工作花了太长时间,下一次回调且需等待runloop释放,因为这次回调被延迟了,那下一次回调将被直接跳过;这种情况下,如果计划提前开始绘制下一帧时,需要注意这里的可用时间是16毫秒,而非正常的8毫秒;为了追踪到这个时间差,可以记录上一次targetTimestamp与本次targetTimestamp来准确获得这个时间。
因此基于targetTimestamp和timestamp差值来判断当前是否回调被跳过,进而提前渲染之后帧的操作,会导致每次回调被丢失时,您自定义提前绘图也减慢了一帧;而追踪targetTimestamp和 上一次targetTimestamp之间的差值,来保证获取正确的剩余时间,进而可以在回调被跳过时,正确提前绘制下一帧;当然如果您的绘制任务很大,建议基于targetTimestamp提供的值来动态调整绘制工作量,以达成这里的绘制时间要求。
总结上述 ProMotion 最佳实践
回顾本Session 10147,我们先讨论了macOS中的Adaptive-Sync动态帧速率技术,以及如何基于此技术为用户提供更加顺滑的渲染效果体验;之后,我们讨论了如何在 iPad Pro 和 iPhone 13 Pro设备中基于ProMotion技术的CADisplayLink最佳实践,__请注意__这两种显示技术之间的区别,以及最佳实践的不同;随着显示技术的不断发展,我们希望本篇文章为您在日益动态的显示时序技术应用中提供一些帮助;
在此向您推荐WWDC17 - Introducing Metal 2和 WWDC19 - Delivering Optimized Metal Apps And Games。