android帧的绘制过程以及fps的获取
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了android帧的绘制过程以及fps的获取相关的知识,希望对你有一定的参考价值。
参考技术A 帧的渲染过程中一些关键组件的流程图任何可以产生图形信息的组件都统称为图像的生产者,比如OpenGL ES, Canvas 2D, 和 媒体解码器等。
SurfaceFlinger是最常见的图像消费者,Window Manager将图形信息收集起来提供给SurfaceFlinger,SurfaceFlinger接受后经过合成再把图形信息传递给显示器。同时,SurfaceFlinger也是唯一一个能够改变显示器内容的服务。SurfaceFlinger使用OpenGL和Hardware Composer来生成surface.
某些OpenGL ES 应用同样也能够充当图像消费者,比如相机可以直接使用相机的预览界面图像流,一些非GL应用也可以是消费者,比如ImageReader 类。
Window Manager是一个用于控制window的系统服务,包含一系列的View。每个Window都会有一个surface,Window Manager会监视window的许多信息,比如生命周期、输入和焦点事件、屏幕方向、转换、动画、位置、转换、z-order等,然后将这些信息(统称window metadata)发送给SurfaceFlinger,这样,SurfaceFlinger就能将window metadata合成为显示器上的surface。
为硬件抽象层(HAL)的子系统。SurfaceFlinger可以将某些合成工作委托给Hardware Composer,从而减轻OpenGL和GPU的工作。此时,SurfaceFlinger扮演的是另一个OpenGL ES客户端,当SurfaceFlinger将一个缓冲区或两个缓冲区合成到第三个缓冲区时,它使用的是OpenGL ES。这种方式会比GPU更为高效。
一般应用开发都要将UI数据使用Activity这个载体去展示,典型的Activity显示流程为:
一般app而言,在任何屏幕上起码有三个layer:
那么android是如何使用这两种合成机制的呢?这里就是Hardware Composer的功劳。处理流程为:
借用google一张图说明,可以将上面讲的很多概念展现,很清晰。地址位于 https://source.android.com/devices/graphics/
即 Frame Rate,单位 fps,是指 gpu 生成帧的速率,如 33 fps,60fps,越高越好。
但是对于快速变化的游戏而言,你的FPS很难一直保持同样的数值,他会随着你所看到的显示卡所要描画的画面的复杂程度而变化。
安卓系统中有 2 种 VSync 信号:
如上图,CPU/GPU 向 Buffer 中生成图像,屏幕从 Buffer 中取图像、刷新后显示。这是一个典型的生产者——消费者模型。理想的情况是帧率和刷新频率相等,每绘制一帧,屏幕显示一帧。而实际情况是,二者之间没有必然的大小关系,如果没有锁来控制同步,很容易出现问题。
所谓”撕裂”就是一种画面分离的现象,这样得到的画像虽然相似但是上半部和下半部确实明显的不同。这种情况是由于帧绘制的频率和屏幕显示频率不同步导致的,比如显示器的刷新率是75Hz,而某个游戏的FPS是100. 这就意味着显示器每秒更新75次画面,而显示卡每秒更新100次,比你的显示器快33%。
两个缓存区分别为 Back Buffer 和 Frame Buffer。GPU 向 Back Buffer 中写数据,屏幕从 Frame Buffer 中读数据。VSync 信号负责调度从 Back Buffer 到 Frame Buffer 的复制操作,可认为该复制操作在瞬间完成。
双缓冲的模型下,工作流程这样的:
应用和SurfaceFlinger的渲染回路必须同步到硬件的VSYNC,在一个VSYNC事件中,显示器将显示第N帧,SurfaceFlinger合成第N+1帧,app合成第N+2帧。
使用VSYNC同步可以保证延迟的一致性,减少了app和SurfaceFlinger的错误,以及显示在各个阶段之间的偏移。然而,前提是app和SurfaceFlinger每帧时间的变化并不大。因此,从输入到显示的延迟至少有两帧。
为了解决这个问题,您可以使用VSYNC偏移量来减少输入到显示的延迟,其方法为将app和SurfaceFlinger的合成信号与硬件的VSYNC关联起来。因为通常app的合成耗时是小于两帧的(33ms左右)。
VSYNC偏移信号细分为以下3种,它们都保持相同的周期和偏移向量:
注意,当 VSync 信号发出时,如果 GPU/CPU 正在生产帧数据,此时不会发生复制操作。屏幕进入下一个刷新周期时,从 Frame Buffer 中取出的是“老”数据,而非正在产生的帧数据,即两个刷新周期显示的是同一帧数据。这是我们称发生了“掉帧”(Dropped Frame,Skipped Frame,Jank)现象。
第一列t1: when the app started to draw (开始绘制图像的瞬时时间)
第二列t2: the vsync immediately preceding SF submitting the frame to the h/w (VSYNC信令将软件SF帧传递给硬件HW之前的垂直同步时间),也就是对应上面所说的软件Vsync
第三列t3: timestamp immediately after SF submitted that frame to the h/w (SF将帧传递给HW的瞬时时间,及完成绘制的瞬时时间)
每dumpsys SurfaceFlinger一次计算汇总出一个fps,计算规则为:
frame的总数N:127行中的非0行数
绘制的时间T:设t=当前行t2 - 上一行的t2,求出所有行的和∑t
fps=N/T (要注意时间转化为秒)
一次dumpsys SurfaceFlinger会输出127帧的信息,但是这127帧可能是这个样子:
如果t3-t1>16.7ms,则认为发生一次卡顿
设目标fps为target_fps,目标每帧耗时为target_ftime=1000/target_fps
从以下几个维度衡量流畅度:
参考文章:
http://windrunnerlihuan.com/2017/05/21/VSync%E4%BF%A1%E5%8F%B7/
如何在 chrome devtools 中获取 FPS
【中文标题】如何在 chrome devtools 中获取 FPS【英文标题】:How to get the FPS in chrome devtools 【发布时间】:2018-01-03 14:45:50 【问题描述】:我想检索测量的性能记录的平均 fps。
到目前为止,我只能通过将鼠标悬停在这样的帧上来获得每帧的持续时间和 fps:
或通过选择框架:
要得到所有帧的平均fps,我必须手动将它们一一相加,这很不方便。
例如,Firefox devtools 在面板的右上角显示平均 fps。
【问题讨论】:
页面本身只有一个单独的 FPS 仪表覆盖层,您可以在 devtools bottom drawer ->Rendering
-> FPS meter
中启用它。
@drawer 但这只会显示当前的 fps 而不是平均值,还是我遗漏了什么?
显然没有实现。另见crbug.com/627925
如果您需要此功能,请评论并为 wOxxOm 链接到的 Crbug 问题加注星标。目前它被归档为低优先级。
【参考方案1】:
您可以使用 devtools-for-devtools。
-
将 devtools 切换到分离窗口模式(单击 devtools 设置图标,单击“取消停靠”图标)。下次您只需按 Ctrl-Shift-D 即可切换模式。
按 Ctrl-Shift-i 调用 devtools-for-devtools
显示所有帧的 FPS:
UI.panels.timeline._flameChart._model._frameModel._frames.slice(1).map(f => +(1000 / f.duration).toFixed(1))
显示平均 FPS:
+UI.panels.timeline._flameChart._model._frameModel._frames.slice(1).map(f => 1000 / f.duration).reduce((avg, fps, i) => (avg*i + fps) / (i+1), 0).toFixed(1)
您可以将此代码保存为 devtools Snippets panel 中的 sn-ps 并在上述步骤 2 之后调用它。
【讨论】:
这太棒了!我不知道我可以在 devtools 窗口中使用 devtools。 对于那些可能对在哪里添加它感到困惑的人,打开第二个 DevTool 后,将上面的代码粘贴到控制台中。【参考方案2】:感谢@wOxxOm 指出如何在the answer above 中为 DevTools 访问 DevTools。
但是,计算平均 FPS 的代码并不完全正确。例如,如果有一帧渲染需要一秒钟,那么该帧的 fps 为 1。如果有另一个帧需要 (1000 / 60)
毫秒来渲染,那么该帧的 fps 为 60。原始代码将给出这两个帧的平均 fps 为 (60 + 1) / 2
,这是不正确的。
正确的平均 fps 是总帧数除以总时长。在这个例子中,它是2 / (1 + 1 / 60)
fps。
实现这一点的一种方法是:
function averageFps()
let frames = UI.panels.timeline._flameChart._model._frameModel._frames;
let duration = (frames[frames.length - 1].endTime - frames[0].startTime) / 1000;
let average = frames.length / duration
return +average.toFixed(2);
【讨论】:
【参考方案3】:请注意,API 似乎对此略有更改,因此现在完成此操作的新代码是:
let frames = UI.panels.timeline.flameChart.model.frameModelInternal.frames;
let frameSet = [];
let startTimeMs = UI.panels.timeline.flameChart.model.window().left;
let endTimeMs = UI.panels.timeline.flameChart.model.window().right;
let minFPS = 1000;
let maxFPS = -1;
let totalFPS = 0;
for (let frameIdx in frames)
let frame = frames[frameIdx];
if (frame.startTime >= startTimeMs && endTimeMs >= frame.endTime)
frameSet.push(frame);
let frameRate = (16.0/frame.duration) * 60;
if (maxFPS < frameRate)
maxFPS = frameRate;
if (minFPS > frameRate)
minFPS = frameRate;
totalFPS += frameRate;
console.log(`Total Frames: $frameSet.length`);
console.log(`Min FPS: $minFPS`);
console.log(`Max FPS: $maxFPS`);
console.log(`Average FPS: $totalFPS / frameSet.length`);
【讨论】:
有人知道这个UI
API 上是否有文档吗?
我喜欢你的 sn-p,但是对于 97.0.4692.99 版的 Chrome,我仍然需要使用 API 的下划线版本。这个新的 API 什么时候开始发挥作用?
@Chris Blackwell 让我检查一下我的 Chrome 版本并回复你。
@ChrisBlackwell 看起来我在 Ubuntu Linux(实际上是 PopOS,但它基于 Ubuntu)上使用的是 96.0.4664.110 版本。我想知道我是否有一个 old API。我会看看我是否可以调查一下。【参考方案4】:
更新了@Daniel Le 考虑当前选定范围的解决方案
var startTime = UI.panels.timeline._flameChart._model._window.left;
var endTime = UI.panels.timeline._flameChart._model._window.right;
var frames = UI.panels.timeline._flameChart._model._frameModel._frames
.filter(frame => (frame.startTime > startTime) && (frame.endTime < endTime));
var duration = (frames[frames.length - 1].endTime - frames[0].startTime) / 1000;
var average = frames.length / duration
console.log(+average.toFixed(2));
【讨论】:
【参考方案5】:更新代码: 显示所有帧的 FPS:
UI.panels.timeline.flameChart.model
.frameModel().frames.slice(1).map(f => +(1000 / f.duration).toFixed(1))
显示平均 FPS:
UI.panels.timeline.flameChart.model.frameModel()
.frames.slice(1).map(f => 1000 / f.duration)
.reduce((avg, fps, i) => (avg*i + fps) / (i+1), 0).toFixed(1)
【讨论】:
以上是关于android帧的绘制过程以及fps的获取的主要内容,如果未能解决你的问题,请参考以下文章