又卡了~从王者荣耀看Android屏幕刷新机制
前言
正在带妹子上分的我,团战又卡了,我该怎么向妹子解释?在线等。
“卡”的意思
不管是端游还是手游,我们都会时不时遇到“卡”的时候,一般这个卡有两种含义:
-
掉帧
-
画面撕裂
那么问题来了,这些情况到底是什么原因导致的?又该怎么解决?
掉帧
首先,要知道帧
是什么,帧率
又是什么。
帧,就是影像动画中最小单位的单幅影像画面,相当于电影胶片上的每一格镜头。 一帧就是一幅静止的画面,连续的帧就形成动画,如电视图象等。
帧率(每秒帧数),简单地说,就是在1秒钟时间里传输的图片的帧数,也可以理解为图形处理器每秒钟能够刷新几次,通常用fps(frames per second)表示
这下大家应该知道了,帧就是一个静止画面,很多个帧一起就组成了视频、电影、游戏画面。
而帧率就是我们游戏常见到的fps
,指一秒钟绘制出现的帧数,单位为“赫兹”(hz)
。
这里简单科普下,一般要求连贯性的话,帧数至少要高于每秒约10至12帧的时候,人眼才会认为是连贯的,此现象叫做“视觉暂留现象”
,是由人眼的生物构造决定的。通过这个现象,早期的无声电影通过手摇驱动,将画面快速播放,就能让人感觉在播放完整连续的视频。
按照我们的认知,这个帧率一般是越大越连贯,就越不卡。但同时,带来的消耗也就越多,比如电影需要更多的胶卷,电脑需要更好的硬件支持。所以电影一般通用的帧率为24hz,而电脑、手机一般帧率为60hz,这样就能保证正常条件下能让人舒服得观看和使用。
那掉帧
的意思就很明显了,本来游戏的fps为60,突然降到了20,也就是一秒只有20帧了,那能不卡吗?
那么,掉帧原因到底是啥呢?
其实原因大家都知道,不信你继续看...
硬件原因
“我这个手机玩游戏卡死了”
“你那啥破手机啊,赶快换一个~”
这个对话应该时常发生,所以大家也都清楚,硬件的好坏
一定程度上决定了玩游戏“卡不卡”
,配置高的硬件玩游戏就能保证游戏的流畅。
软件原因
“你这啥app啊,做的啥游戏啊,这么卡,我这手机配置这么高,就玩你这个卡”
“额,可能是游戏优化没做好,”
第二个原因,就是因为游戏/软件
自身的优化就没做好,图片弄的很大,布局嵌套太深,那么帧 的计算和渲染就更耗时间,就会有可能导致掉帧。
网络原因
“不行了,太卡了,我这ping都快1000了,怎么玩啊”
“快换流量啊,团战要输了,少个人怎么打”
对了,第三个原因就是网络原因
,这也是最常发生的原因了,网络的波动会影响 画面 的传输,所以就会掉帧。
屏幕刷新机制
上述三个原因,其实都涉及到屏幕刷新
的基本机制。
在典型的显示系统中,不管是手机还是电脑,一般都涉及到三个部分:
-
cpu
,*处理器。用于计算数据,信息处理。 -
gpu
,图形处理器。用于处理图像图形,也就是俗称的显卡。 -
display
,显示屏幕。用于展示画面,也就是我们的手机屏幕、电脑显示器。
整个显示过程就是:
-
cpu
计算屏幕需要的数据,然后交给gpu。 -
gpu
对图像进行处理绘制,然后存到缓存区。 -
display
再从这个缓存区读取数据,显示出来。
每一帧都是重复这个工作,也就是1秒中需要60次这样循环操作,每次操作需要的时间就约等于16.6ms
。也就是我们常说的android系统中,会每隔16.6ms刷新一次屏幕。
关于屏幕刷新机制,有一张很经典的图片:
可以看到,16.6ms一到,系统就发送了vsync
信号,然后屏幕会从缓存区获取了新的一帧图像并显示出来,与此同时,cpu也开始了下一帧数据的计算,然后计算好交给gpu,最后放到缓存区,等待下一次vsync
信号。
vsync
信号是啥呢?我们暂且按下不表,待会再说,可以先理解它为一种同步刷新信号
,同步cpu和屏幕。当信号来的时候,屏幕开始切换画面,cpu开始下一帧计算。
为了方便理解,我做了个小动画:
通过上面的解释,我们知道了一帧显示的时间是16.6ms
,在这个时间内,cpu和gpu必须把数据处理好并放到缓存区(buffer)
中。
如果在某次的16.6ms中,cpu和gpu没有绘制好下一帧数据,那么display就无法更新下一帧数据了,这就是掉帧
。
所以才有了以上三个原因会导致掉帧,再来回顾下:
-
1、硬件原因
。硬件比较差也就是cpu和gpu计算不给力,导致一帧的数据没准备好,所以掉帧。 -
2、软件原因
。在硬件够用的情况下,app或者游戏的一帧数据计算繁杂,嵌套太多或者图太大,也会导致下一帧数据不能在16.6ms中被准备好,导致掉帧。 -
3、网络原因
。在硬件软件都正常情况下,由于网络波动,cpu的计算数据都没有从网络上获取到,那么肯定会导致cpu数据的准备延迟,最终导致掉帧。
那么掉帧之后,屏幕刷新机制会怎么处理后续的帧呢?
- 如果是
游戏
的话,因为即时性比较重要,所以丢失的帧就不会再去管了,而是直接准备当前时间应该显示的内容,最终显示到屏幕。所以这种情况掉的帧就真的掉了。 - 如果是
应用
的话,可能对数据的完整性比较重要,所以如果第2帧掉了,那么屏幕就继续显示第1帧,而cpu和gpu继续准备第2帧的数据,如果能在下一个16.6ms内完成第2帧数据,那么屏幕就会接着显示第二帧了。比如有时候手机卡的时候,我们去操作app,操作会延迟,就是掉帧了。这种情况帧并不是真的掉了,而是延迟了。
画面撕裂
接下来就看看画面撕裂,为什么一帧中会出现两帧的画面呢?
首先理解一个概念:「逐行扫描」
「逐行扫描」就是说,显示器显示画面并不是“蹭”一下就打出一张画面来,而是从上到下一行一行显示出来的,只不过是显示得比较快所以肉眼看不出来而已。
之前说了屏幕的数据是从缓存区buffer
中取的,如果在屏幕取数据并逐行扫描显示画面的过程中,buffer
中的数据变了,那么就有可能导致画面撕裂。
最明显的例子就是:显卡的fps
是180,而显示器的fps
是60。也就是显卡一秒钟能产生180
张画面,而显示器一秒钟只能读取60
张画面。
那么显示器从buffer中读取数据逐行扫描的过程中,本来需要1/60 秒显示完一张画面,但是在1/180的时间点,显卡就把下一张画面的数据存到buffer了,结果显示器的下半截就显示的是第二张画面的内容了。也就造成了画面撕裂。
再来个动画解释下:
所以为了防止这种状况,一般显示系统会加入一个双缓存+垂直同步
的概念:
- 首先,开启
垂直同步
,就会将gpu的fps限制为和显示器的fps一样。
系统会在显示器绘制完一帧之后发送一个垂直同步信号,然后cpu和gpu就准备下一帧的内容,等待显示器下一帧绘制完,又会发送一个垂直同步信号。如此反复,就限制了显卡的fps,按照显示器的标准来绘制图像。
这个垂直同步信号就叫做 vsync信号
。
玩游戏的朋友应该都知道,很多游戏内设置页都有 垂直同步
的开启选项,为的就是将显卡的fps和显示器的fps适配,防止画面撕裂。
- 其次,通过
双缓存
保证一帧数据的连贯性。
1、缓存区backbuffer
用于cpu/gpu图形处理
2、缓存区framebuffer
用于显示器显示
这样分工明确之后,屏幕只会读取framebuffer
的内容,是一帧完整的画面。而cpu/gpu计算的新一帧内容会放到backbuffer中,不会影响到framebuffer
的内容。
只有当屏幕绘制完一帧内容之后,才会将cpu/gpu计算好的新一帧内容也就是backbuffer
内容和framebuffer
进行交换。
这样就保证了一帧数据的完整连贯。
这里的交换其实就是交换内存地址,两块缓存区a和b,a在第一次充当framebuffer
的角色,b充当backbuffer
的角色。屏幕完成一帧绘制之后,将ab内存地址置换。
当新的一轮vsync信号来的时候,a就充当了backbuffer
的角色,而b就变成了framebuffer
的角色。
小结:当屏幕扫描完第1帧画面之后,系统发送vsync信号
,这时会发生三件事:
- 1、交换两个缓存区(framebuffer、backbuffer)内容。
- 2、显示器开始显示第2帧内容,也就是交换后的framebuffer内容。
- 3、cpu/gpu开始计算处理第三帧的内容,并在处理好内容后放到backbuffer中。
再来个动画:
android project butter(黄油计划)
问题都解决了吗?并没有。
加入vsync信号之后,掉帧问题变得更严重了:
可以发现,加入了vsync
信号后,虽然统一了cpu处理的时间点,但是掉帧问题可能会被再一次放大,从掉一帧直接变成掉两帧。因为中间的16.6ms
被浪费了。
怎么办呢?在保留vsync信号的同时有可能最大利用上cpu/gpu吗?
三缓存来了:
1、缓存区backbuffer
用于cpu/gpu图形处理
2、缓存区triplebuffer
用于cpu/gpu图形处理
3、缓存区framebuffer
用于显示器显示
刚才说的情况导致的原因就是因为在第二个vsync信号来的时候,因为backbuffer
被gpu占用,所以cpu无法去开始新一帧的计算。
加入了第三个缓存区,那么在第二个vsync信号来的时候,cpu就可以利用triplebuffer
开始新一帧的计算,而无视正在被gpu占用的backbuffer
。
你可以理解为 cpu、gpu、display
每个人都有一个缓存区,这样三个就能同时做自己的事而互不影响,最大化利用每个模块。
三缓存和上面说到的vsync同步信号都是 android 4.1
发布的一个project butter(黄油计划)
中提出的,为的是就是让android能让黄油/奶油般顺滑。
最后贴个三缓存机制下的刷新机制图:
小结
今天了解了android系统的刷新机制,虽然没有代码,但是面试中也是常常被问到的,再次总结下:
1、为了解决画面撕裂
问题,引入了垂直同步信号vsync信号
和双缓存
。
- 每次vsync信号到达的时候,屏幕进行画面切换,
cpu/gpu
开始准备下一帧内容。 -
cpu/gpu
每次准备好数据后,放到一个单独的缓存区backbuffer,当屏幕准备好之后,将backbuffer
数据和framebuffer
数据交换,屏幕只读取framebuffer
缓存区的数据,保证了数据的完整连续性。
2、为了解决vsync信号下cpu/gpu
无法最大化利用的问题,引入了三缓存。
当vsync
信号来的时候,即使gpu还没处理好上一帧数据,backbuffer
还不空闲,但是cpu也可以利用第三个缓存区正常开始处理下一帧的数据,最大化利用cpu/gpu
,保证垂直同步机制的同时不浪费资源。
3、掉帧的根本原因是因为在一帧时间内(一般为16.6ms),cpu/gpu无法把下一帧的数据准备好。
即使引用了三缓存和垂直同步
,但是掉帧的情况该发生还是会发生,我们作为app软件开发者,能做的就是尽可能优化布局,减少嵌套,减少cpu/gpu
计算画面数据的时间,让每一帧时间内正常准备好下一帧图像数据。
至于刷新机制在android
源码中到底是怎么实现的呢?这就涉及到choreographer
类的解析。
参考
android project butter分析
framebuffer初探
拜拜
感谢大家的阅读,有一起学习的小伙伴可以关注下我的公众号——码上积木❤️❤️
每日一个知识点,积少成多,建立知识体系架构。
这里有一群很好的android小伙伴,欢迎大家加入~
上一篇: 如何利用vue.js实现拖放功能
下一篇: 捣鼓系列:前端大文件上传