zl程序教程

您现在的位置是:首页 >  其他

当前栏目

[069]PLL_CLK引发的降帧问题

2023-03-15 23:26:15 时间

前言

一个新的项目不管在什么情况下,画面都只能维持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 这几个图画的是真好,仍不住转载一下

可以看到DSI有4根线,就是total_lane_num

如果写的速度慢于扫描的速度,就有可能花屏

尾巴

当然有时间还是想去看看显示驱动那块的代码,给自己留几个问题。 有知道朋友欢迎留言解惑。

presentFence          唤醒的代码位置
GPU completion        唤醒的代码位置
HWC release           唤醒的代码位置