前言
一个新的项目不管在什么情况下,画面都只能维持30帧左右,不能达到60帧。
一般这种问题首先是转给性能组分析,那就让我开始分析吧。
一、最简单的demo
首先我写了一个最简单的demo,看看能不能达到60帧,结果无法只能达到30帧。
1.1 dequeueBuffer时间长
一般就是没有可用的buffer,SurfaceFlinger的消费能力有问题,需要去看SurfaceFlinger的Trace。
1.2 waiting for GPU completion时间长
一般是GPU的性能不行导致了绘制时间过长,但是我的demo就画了一根线,不可能是GPU性能的问题,有可能是GPU没有及时signal,导致了timeout。虽然我没有找到GPU绘制完成signal代码,但是我很快就放弃了这个思路。因为waitForever中虽然有3000ms的timeout温馨提示,但是最后还是会继续等,而且是timeout never。
status_t Fence::waitForever(const char* logname) {
ATRACE_CALL();
if (mFenceFd == -1) {
return NO_ERROR;
}
int warningTimeout = 3000;//温馨提示3000ms,
int err = sync_wait(mFenceFd, warningTimeout);
if (err < 0 && errno == ETIME) {
ALOGE("waitForever: %s: fence %d didn't signal in %u ms", logname, mFenceFd.get(),
warningTimeout);
...
err = sync_wait(mFenceFd, TIMEOUT_NEVER);//这里是time out never
}
return err < 0 ? -errno : status_t(NO_ERROR);
}
加入waiting for HWC release以后,原来是release的fence信号signal慢了,导致的GPU completion的时间也变长了(S平台和之前的平台对于release buffer的流程有所差异)。
为什么waiting for HWC release会慢就需要去看SurfaceFlinger了。
1.3 小结
两个问题点最后都需要指向到SurfaceFlinger,我们继续查看SF的Trace。
PS:以后遇到waiting for GPU completion时间长的问题,不能直接下定论是GPU性能不行。
二、SurfaceFlinger分析
一看SurfaceFlinger发现非常奇怪的事情,sf竟然绘制一帧,丢一帧。
丢一帧的原因是framePending为true,hwcFrameMissed为true,gpuFrameMissed为false。
然后满足了提前return的条件。
void SurfaceFlinger::onMessageInvalidate(int64_t vsyncId, nsecs_t expectedVSyncTime) {
....
// Pending frames may trigger backpressure propagation.
const TracedOrdinal<bool> framePending = {"PrevFramePending",
previousFramePending(graceTimeForPresentFenceMs)};
const TracedOrdinal<bool> frameMissed = {"PrevFrameMissed",
framePending ||
(previousPresentTime >= 0 &&
(lastScheduledPresentTime <
previousPresentTime - frameMissedSlop))};
const TracedOrdinal<bool> hwcFrameMissed = {"PrevHwcFrameMissed",
mHadDeviceComposition && frameMissed};
const TracedOrdinal<bool> gpuFrameMissed = {"PrevGpuFrameMissed",
mHadClientComposition && frameMissed};
....
// framePending true
// frameMissed true
// hwcFrameMissed true
// gpuFrameMissed false
if (framePending) {
if ((hwcFrameMissed && !gpuFrameMissed) || mPropagateBackpressureClientComposition) {
signalLayerUpdate();
return;//满足条件提前返回。
}
}
...
}
而且从图中看到,waiting for presentFence,而且整个wait过程竟然需要27.4ms。也就说sf合成后到开始刷新这一帧到屏幕需要27ms。一时,我也无法继续跟踪下去了,因为对HWC我不是很熟悉。
三、PLL_CLK值有问题
好在驱动工程师突然告诉我说PLL_CLK有问题,从475改成了560问题就解决了。
当时我就一面懵逼,PLL_CLK是什么东西,这个数值代表什么意思。
3.1 PLL_CLK是什么
PLL_CLK就是图中CLK的那段波的频率,也就每秒一次高低电频发生的次数。
转自诺比亚团队
3.2 CMD屏PLL_CLK计算公式
(Data rate) = width * height * 1.2 * total_bit_per_pixel * frame_per_second / total_lane_num
DSI采用的是双边采样,则clk等于数据速率的一半,也就是说一个clk周期内传送2位,所以你计算出来的值还要除以2
即PLL_CLOCK = Data rate / 2 (单位是MHZ)
PS:其中1.2应该是一个经验值。
经过计算我们屏幕PLL_CLK合适的值应该是559左右
width = 1080 (屏幕分辨率是1080 * 2400)
height = 2400
total_bit_per_pixel = 24 (RGB值,每个字节是8位,三个字节)
frame_per_second = 60 (60帧的屏幕)
total_lane_num = 4(4根线)
Data rate = 1080 * 2400 * 1.2 * 24 * 60 / 4 = 1119744000
即PLL_CLOCK = Data rate / 2 = 559872000 = 559.872MHZ
公式可能看不明白,这样子解释你就明白了。
1秒内60hz的手机需要传递的数据是多少。
屏幕的宽x屏幕的高x每个像素点的数据量x每秒的帧率。
1080x2400x24x60
由于有4根传输线,并且一次高低电频可以传输2次,所以PLL_CLOCK至少要达到以下数值
1080x2400x24x60/4/2
但是不能那么小气,加上一个经验值1.2
1080x2400x24x60x1.2/4/2 = 559872000 = 559.872MHZ
3.3 小结
之前设置的PLL_CLK值过小,传输速率过低,导致前一帧无法在一个vsync周期内将屏幕的数据传输给屏幕,导致这一帧的presentFence等待signal时间过久,然后sf主动丢了一帧,从而导致屏幕从60fps降为了30fps。但是目前presentFence和传输数据给屏幕之前的关系,我还没有找到对应的代码,因为我对驱动不是很熟悉。
四、整个过程还原
可以用已经掌握的知识来还原整个上层的流程,整个过程更加清晰了。
总结
整个问题还是非常有意思的,强烈推荐大家阅读参考资料中的文章,让我对屏幕显示画面有了更加深入的理解,而且也终于理解了为什么画面会有出现撕裂。
参考资料
https://www.jianshu.com/p/df46e4b39428
这几个图画的是真好,仍不住转载一下
如果写的速度慢于扫描的速度,就有可能花屏
尾巴
当然有时间还是想去看看显示驱动那块的代码,给自己留几个问题。
有知道朋友欢迎留言解惑。
presentFence 唤醒的代码位置
GPU completion 唤醒的代码位置
HWC release 唤醒的代码位置
网友评论