[069]PLL_CLK引發(fā)的降幀問(wèn)題

前言

一個(gè)新的項(xiàng)目不管在什么情況下,畫面都只能維持30幀左右,不能達(dá)到60幀。
一般這種問(wèn)題首先是轉(zhuǎn)給性能組分析,那就讓我開始分析吧。

一、最簡(jiǎn)單的demo

首先我寫了一個(gè)最簡(jiǎn)單的demo,看看能不能達(dá)到60幀,結(jié)果無(wú)法只能達(dá)到30幀。


1.1 dequeueBuffer時(shí)間長(zhǎng)

一般就是沒(méi)有可用的buffer,SurfaceFlinger的消費(fèi)能力有問(wèn)題,需要去看SurfaceFlinger的Trace。

1.2 waiting for GPU completion時(shí)間長(zhǎng)

一般是GPU的性能不行導(dǎo)致了繪制時(shí)間過(guò)長(zhǎng),但是我的demo就畫了一根線,不可能是GPU性能的問(wèn)題,有可能是GPU沒(méi)有及時(shí)signal,導(dǎo)致了timeout。雖然我沒(méi)有找到GPU繪制完成signal代碼,但是我很快就放棄了這個(gè)思路。因?yàn)閣aitForever中雖然有3000ms的timeout溫馨提示,但是最后還是會(huì)繼續(xù)等,而且是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以后,原來(lái)是release的fence信號(hào)signal慢了,導(dǎo)致的GPU completion的時(shí)間也變長(zhǎng)了(S平臺(tái)和之前的平臺(tái)對(duì)于release buffer的流程有所差異)。
為什么waiting for HWC release會(huì)慢就需要去看SurfaceFlinger了。


1.3 小結(jié)

兩個(gè)問(wèn)題點(diǎn)最后都需要指向到SurfaceFlinger,我們繼續(xù)查看SF的Trace。
PS:以后遇到waiting for GPU completion時(shí)間長(zhǎng)的問(wèn)題,不能直接下定論是GPU性能不行。

二、SurfaceFlinger分析

一看SurfaceFlinger發(fā)現(xiàn)非常奇怪的事情,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,而且整個(gè)wait過(guò)程竟然需要27.4ms。也就說(shuō)sf合成后到開始刷新這一幀到屏幕需要27ms。一時(shí),我也無(wú)法繼續(xù)跟蹤下去了,因?yàn)閷?duì)HWC我不是很熟悉。

三、PLL_CLK值有問(wèn)題

好在驅(qū)動(dòng)工程師突然告訴我說(shuō)PLL_CLK有問(wèn)題,從475改成了560問(wèn)題就解決了。
當(dāng)時(shí)我就一面懵逼,PLL_CLK是什么東西,這個(gè)數(shù)值代表什么意思。

3.1 PLL_CLK是什么

PLL_CLK就是圖中CLK的那段波的頻率,也就每秒一次高低電頻發(fā)生的次數(shù)。


轉(zhuǎn)自諾比亞團(tuán)隊(duì)

3.2 CMD屏PLL_CLK計(jì)算公式

(Data rate) = width * height * 1.2 * total_bit_per_pixel * frame_per_second / total_lane_num
DSI采用的是雙邊采樣,則clk等于數(shù)據(jù)速率的一半,也就是說(shuō)一個(gè)clk周期內(nèi)傳送2位,所以你計(jì)算出來(lái)的值還要除以2
即PLL_CLOCK = Data rate / 2 (單位是MHZ)
PS:其中1.2應(yīng)該是一個(gè)經(jīng)驗(yàn)值。

經(jīng)過(guò)計(jì)算我們屏幕PLL_CLK合適的值應(yīng)該是559左右

width = 1080 (屏幕分辨率是1080 * 2400)
height = 2400
total_bit_per_pixel = 24 (RGB值,每個(gè)字節(jié)是8位,三個(gè)字節(jié))
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秒內(nèi)60hz的手機(jī)需要傳遞的數(shù)據(jù)是多少。
屏幕的寬x屏幕的高x每個(gè)像素點(diǎn)的數(shù)據(jù)量x每秒的幀率。
1080x2400x24x60
由于有4根傳輸線,并且一次高低電頻可以傳輸2次,所以PLL_CLOCK至少要達(dá)到以下數(shù)值
1080x2400x24x60/4/2
但是不能那么小氣,加上一個(gè)經(jīng)驗(yàn)值1.2
1080x2400x24x60x1.2/4/2 = 559872000 = 559.872MHZ

3.3 小結(jié)

之前設(shè)置的PLL_CLK值過(guò)小,傳輸速率過(guò)低,導(dǎo)致前一幀無(wú)法在一個(gè)vsync周期內(nèi)將屏幕的數(shù)據(jù)傳輸給屏幕,導(dǎo)致這一幀的presentFence等待signal時(shí)間過(guò)久,然后sf主動(dòng)丟了一幀,從而導(dǎo)致屏幕從60fps降為了30fps。但是目前presentFence和傳輸數(shù)據(jù)給屏幕之前的關(guān)系,我還沒(méi)有找到對(duì)應(yīng)的代碼,因?yàn)槲覍?duì)驅(qū)動(dòng)不是很熟悉。

四、整個(gè)過(guò)程還原

可以用已經(jīng)掌握的知識(shí)來(lái)還原整個(gè)上層的流程,整個(gè)過(guò)程更加清晰了。


總結(jié)

整個(gè)問(wèn)題還是非常有意思的,強(qiáng)烈推薦大家閱讀參考資料中的文章,讓我對(duì)屏幕顯示畫面有了更加深入的理解,而且也終于理解了為什么畫面會(huì)有出現(xiàn)撕裂。

參考資料

http://www.itdecent.cn/p/df46e4b39428
這幾個(gè)圖畫的是真好,仍不住轉(zhuǎn)載一下

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

如果寫的速度慢于掃描的速度,就有可能花屏

尾巴

當(dāng)然有時(shí)間還是想去看看顯示驅(qū)動(dòng)那塊的代碼,給自己留幾個(gè)問(wèn)題。
有知道朋友歡迎留言解惑。

presentFence          喚醒的代碼位置
GPU completion        喚醒的代碼位置
HWC release           喚醒的代碼位置
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容