观看麻豆影视文化有限公司-国产 高清 在线-国产 日韩 欧美 亚洲-国产 日韩 欧美 综合-日日夜夜免费精品视频-日日夜夜噜

小米電池休眠自動(dòng)解除(小米電池休眠自動(dòng)解除打開(kāi)后蓋)

  • 生活
  • 2023-04-19 14:20
背景

Apple在今年推出了支持ProMotion屏幕的iPhone設(shè)備,讓App在iPhone13Pro和iPhone13ProMax上的最大刷新幀率可到達(dá)120Hz,極大優(yōu)化了應(yīng)用滑動(dòng)/動(dòng)畫(huà)的流暢度體驗(yàn)。

ProMotion并不是一個(gè)新的概念,早在2017年,Apple推出的第二代iPadPro便搭載了這一刷新率最高可達(dá)120Hz的屏幕。在iPad上,高刷新率默認(rèn)對(duì)所有App啟用。而也許是出于能耗的考慮,在iPhone上,Apple并未將這個(gè)能力自動(dòng)對(duì)所有App啟用,而是需要開(kāi)發(fā)者手動(dòng)添加配置項(xiàng)來(lái)進(jìn)行適配。

近期有消息指出iOS15.4beta修正了這一行為(https://www.macrumors.com/2022/01/27/ios-15-4-apps-120-hz-promotion/),經(jīng)過(guò)筆者驗(yàn)證額外的配置項(xiàng)依然是需要的,并且本文內(nèi)容依然適用。

本文介紹了在iPhone上對(duì)ProMotion動(dòng)態(tài)幀率的適配時(shí)觀察到的現(xiàn)象和遇到的問(wèn)題,嘗試推測(cè)了背后的原理,并探討了解決問(wèn)題的可能思路,最終基于調(diào)研結(jié)果在國(guó)際化短視頻業(yè)務(wù)上線優(yōu)化方案,取得了核心業(yè)務(wù)指標(biāo)的收益。

什么是幀率

在深入探究ProMotion屏幕所帶來(lái)的變化之前,我們先回顧一個(gè)似乎耳熟能詳?shù)母拍睿?/p>

什么是幀率?

眾所周知,顯示器并不能顯示真正動(dòng)態(tài)的畫(huà)面,所有動(dòng)畫(huà)效果都是靠高速播放一幀幀靜態(tài)畫(huà)面欺騙人類(lèi)視覺(jué)所造成的假象。那么幀率最基本的定義便是屏幕內(nèi)容的變化頻率,是一個(gè)物理意義上的指標(biāo)。這種變化頻率又由以下兩個(gè)值共同決定:

刷新幀率:由屏幕硬件規(guī)格控制,傳統(tǒng)顯示設(shè)備一般為59.94Hz,決定了幀率的上限。渲染幀率:由CPU->GPU渲染管線的執(zhí)行速率控制,決定了幀率的下限。

理想情況下,渲染幀率和刷新幀率最好完全匹配,或者渲染幀率是刷新幀率的整數(shù)倍,這樣實(shí)際展現(xiàn)的內(nèi)容不會(huì)出現(xiàn)任何異常。但現(xiàn)實(shí)中二者往往會(huì)出現(xiàn)不匹配的情況,卡頓就是其中之一:

卡頓

當(dāng)CPU->GPU的渲染管線遇到瓶頸,導(dǎo)致某一幀的渲染耗時(shí)大于屏幕的刷新間隔時(shí),上一幀畫(huà)面會(huì)在屏幕上多停留數(shù)幀的時(shí)間。當(dāng)這個(gè)滯留時(shí)間過(guò)長(zhǎng),用戶(hù)感知到畫(huà)面更新的延遲,這稱(chēng)為卡頓。這也是iOS開(kāi)發(fā)過(guò)程中會(huì)遇到的主要性能問(wèn)題之一。

實(shí)際幀率

幀率并不等同于刷新率,它和所展示的內(nèi)容息息相關(guān):

展示靜態(tài)畫(huà)面時(shí),理想情況只需要進(jìn)行一次渲染,盡管屏幕仍然以60Hz或者更高的頻率進(jìn)行刷新,每次刷新所展示的內(nèi)容(FrameBuffer)也未改變,用戶(hù)感知到的實(shí)際幀率依然接近0。展示固定幀率的元素,例如24FPS的電影視頻時(shí),用戶(hù)感知到的實(shí)際幀率自然也是24FPS左右。展示超高幀率的內(nèi)容,例如CS:GO不鎖幀跑>200FPS,但由于顯示設(shè)備刷新率限制,用戶(hù)感知到的幀率依然不會(huì)超過(guò)硬件幀率的上限。什么是動(dòng)態(tài)刷新率

ProMotion本質(zhì)上是對(duì)Adaptive-Sync顯示標(biāo)準(zhǔn)的一種實(shí)現(xiàn)。

Ref:https://en.***.org/wiki/Variable_refresh_rate

根據(jù)Apple官方文檔顯示,ProMotion屏幕支持的刷新率是可變的。

具體來(lái)說(shuō),對(duì)iPhone而言:

TheiPhone13ProandiPhone13ProMaxProMotiondisplayscanpresentcontentonthedisplayusingthefollowingrefreshratesandtimings:

120Hz(8ms),80Hz(12ms),60Hz(16ms),48Hz(20ms),40Hz(25ms),30Hz(33ms),24Hz(41ms),20Hz(50ms),16Hz(62ms),15Hz(66ms),12Hz(83ms),10Hz(100ms)

而對(duì)iPadPro來(lái)說(shuō):

TheiPadPro’sProMotiondisplaycanpresentcontentonthedisplayusingthefollowingrefreshratesandtimings:

120Hz(8ms),60Hz(16ms),40Hz(25ms),30Hz(33ms),24Hz(41ms)

這其實(shí)是Apple對(duì)VESA定制的Adaptive-Sync技術(shù)標(biāo)準(zhǔn)的一種實(shí)現(xiàn),在游戲業(yè)界已經(jīng)實(shí)裝多年,類(lèi)似的實(shí)現(xiàn)還有AMD的FreeSync和Nivida的G-Sync。這種新的顯示技術(shù)有著以下優(yōu)點(diǎn):

減少可感知的卡頓

對(duì)于固定刷新率的屏幕而言,當(dāng)某一幀的渲染耗時(shí)出現(xiàn)異常,在VSync信號(hào)到來(lái)之后才完成渲染,那么當(dāng)前內(nèi)容便會(huì)滯留在屏幕上,這一幀需要再等一次VSync信號(hào)才能被渲染展示給用戶(hù)。

而Adaptive-Sync技術(shù)可以避免這一點(diǎn),在該幀渲染結(jié)束后盡快進(jìn)行展示,從而減少顯示卡頓時(shí)長(zhǎng):

減少移動(dòng)設(shè)備的屏幕功耗

在搭載了固定刷新率屏幕的設(shè)備上,當(dāng)顯示靜態(tài)內(nèi)容或者幀率較低(例如視頻)的內(nèi)容時(shí),GPU的渲染頻率比實(shí)際頻率刷新率會(huì)更低。但是固定刷新率的屏幕依然會(huì)已最高速率進(jìn)行刷新,重復(fù)展示之前的內(nèi)容,造成了額外的電量消耗。

ProMotion屏幕在這種情況下可以主動(dòng)降低刷新率,減少屏幕功耗,這對(duì)于移動(dòng)設(shè)備來(lái)說(shuō)尤其重要。

動(dòng)態(tài)刷新率的表現(xiàn)形式

TheiPhone13Pro,theiPhone13ProMax,andtheiPadProProMotiondisplaysarecapableofdynamicallyswitchingbetween:

Fasterrefreshratesupto120Hz

Slowerrefreshratesdownto24Hzor10Hz

已知,ProMotion屏幕的刷新幀率并不固定,系統(tǒng)會(huì)實(shí)時(shí)地根據(jù)當(dāng)前顯示內(nèi)容的類(lèi)型和狀態(tài)來(lái)動(dòng)態(tài)切換屏幕的刷新幀率。為了更好地理解這種動(dòng)態(tài)幀率的表現(xiàn)形式,筆者分別在

iPhoneXR-無(wú)ProMotioniPhone13Pro-有ProMotion默認(rèn)鎖頻

上對(duì)一些典型渲染場(chǎng)景進(jìn)行了測(cè)試,發(fā)現(xiàn)搭載了ProMotion屏幕的設(shè)備上運(yùn)行App時(shí),不同的場(chǎng)景下的各種統(tǒng)計(jì)口徑的幀率指標(biāo)確實(shí)展示出了有趣的變化。

具體而言,筆者分別在以下幾種場(chǎng)景:

測(cè)試場(chǎng)景靜態(tài)頁(yè)面

靜態(tài)的UIView,無(wú)動(dòng)畫(huà)/視頻等元素

2.滑動(dòng)中的頁(yè)面

包含靜態(tài)Cell的UITableView,僅觀察滑動(dòng)中的表現(xiàn)

3.CoreAnimation默認(rèn)刷新率動(dòng)畫(huà)

顯示基于CABasicAnimation實(shí)現(xiàn)的簡(jiǎn)單位移動(dòng)畫(huà)

4.CoreAnimation120Hz高刷新率動(dòng)畫(huà)

僅在ProMotion設(shè)備上測(cè)試,基于CABasicAnimation實(shí)現(xiàn)的簡(jiǎn)單位移動(dòng)畫(huà),同時(shí)解鎖了CADisableMinimumFrameDurationOnPhone和preferredFrameRateRange幀率限制。(關(guān)于此限制下文會(huì)有具體介紹)

5.Metal渲染30Hz/60Hz視頻

使用基于MTKView進(jìn)行渲染的播放器,播放源幀率分別為30Hz/60Hz的視頻文件

并使用以下幾種統(tǒng)計(jì)口徑的幀率指標(biāo)進(jìn)行測(cè)試:

測(cè)試指標(biāo)CADisplayLink計(jì)算幀率

iOS中主要的幀率統(tǒng)計(jì)手段。

根據(jù)CADisplayLink.h頭文件中描述,CADisplayLink是一個(gè)”Classrepresentingatimerboundtothedisplayvsync“。在回調(diào)中比較當(dāng)前幀/前一幀的時(shí)間戳,可以計(jì)算出上一幀的渲染耗時(shí)(ts),其倒數(shù)(1/ts)即為當(dāng)前的實(shí)時(shí)幀率。

2.XcodeGPUReport幀率

Xcode->ShowDebugNavigator->FPS中顯示的幀率。這個(gè)只能統(tǒng)計(jì)當(dāng)前應(yīng)用直接通過(guò)OpenGLES或者M(jìn)etal進(jìn)行繪制的幀率,例如游戲渲染/視頻播放,無(wú)法統(tǒng)計(jì)CoreAnimation的幀率(眾所周知,后者通過(guò)backboardd進(jìn)行繪制)。

3.InstrumentsCoreAnimationFPS

Instruments中CoreAnimationFPS工具所顯示的幀率。這個(gè)統(tǒng)計(jì)的是CoreAnimation的幀率,即RenderServerbackboardd繪制的頻率。目前該工具有BUG無(wú)法顯示高于60FPS的幀率。

4.InstrumentsDisplay/VSync信號(hào)頻率

Instruments中Display工具所顯示的Surface/VSync信號(hào)時(shí)間戳。如下圖所示:

Display:指對(duì)應(yīng)顯示器的單個(gè)Surface上屏持續(xù)的時(shí)間,對(duì)應(yīng)CPU-GPU管線的渲染頻率VSync:指垂直同步信號(hào)時(shí)間戳,對(duì)應(yīng)屏幕硬件的刷新頻率

在60Hz屏幕上,iOS設(shè)備默認(rèn)采用雙緩沖刷新機(jī)制,也就是前幀緩存和后幀緩存。GPU總是在后幀緩存上進(jìn)行當(dāng)前幀的繪制。當(dāng)VSync信號(hào)到來(lái)時(shí),交換前后幀緩存的指針(SwapFrameBuffer),屏幕刷新顯示新的內(nèi)容。

而當(dāng)屏幕以120Hz顯示內(nèi)容時(shí),iOS會(huì)切換成三緩沖刷新機(jī)制(見(jiàn)上圖中三種顏色的Surface),這減少渲染管線的壓力,但同時(shí)會(huì)增加一定的渲染上屏延遲。

Metal應(yīng)用可以通過(guò)設(shè)置-[CAMetalLayersetMaximumDrawableCount:]為2來(lái)在120Hz屏幕上強(qiáng)制啟用雙緩沖機(jī)制,避免這種延遲。

如果屏幕顯示內(nèi)容未發(fā)生變化,Surface則不會(huì)發(fā)生交換,一個(gè)Surface的Display可能持續(xù)數(shù)個(gè)VSync間隔,但多余的VSync信號(hào)依然代表著硬件層額外的屏幕刷新,造成額外的電量消耗。

非ProMotion設(shè)備

首先讓我們看看傳統(tǒng)的固定刷新率的設(shè)備的情況。

VSync信號(hào)間隔固定為16.67ms

XR的屏幕刷新率為固定的60Hz,這一點(diǎn)對(duì)應(yīng)的具體指標(biāo)是VSync信號(hào)的間隔,而在任何場(chǎng)景下,XR的VSync信號(hào)的間隔均為固定的16.67ms。

此外,在顯示靜態(tài)內(nèi)容時(shí),由于視圖LayerTree無(wú)變化,CoreAnimation不會(huì)有提交新的事務(wù)提交,backboardd不會(huì)進(jìn)行刷新,所以對(duì)應(yīng)這一幀的Surface也長(zhǎng)時(shí)間(數(shù)十秒)未被交換下去,CoreAnimationFPS的值顯示為0。

但由于VSync信號(hào)仍然以60Hz的頻率持續(xù)觸發(fā),屏幕此時(shí)正在不停重復(fù)展示同樣的FrameBuffer,消耗了額外的電量。

CADisplayLink基本完全跟隨VSync信號(hào)

根據(jù)過(guò)去對(duì)iOS系統(tǒng)的認(rèn)知,我們知道CADisplayLink是由VSync信號(hào)驅(qū)動(dòng)的:

默認(rèn)配置的CADisplayLink的回調(diào)應(yīng)該與VSync信號(hào)基本同時(shí)。

這一點(diǎn)在XR上得到了驗(yàn)證,用Instruments記錄一次主線程發(fā)生的卡頓,得到:

其中:

第一行runloop記錄每次RunLoopAfterWaiting->BeforeWaiting的間隔第二行tick記錄默認(rèn)配置的CADisplayLink回調(diào)間的間隔最下面則是硬件Display/VSync事件時(shí)序圖

可以觀察到下述現(xiàn)象,符合我們之前的對(duì)DisplayLink的認(rèn)識(shí):

沒(méi)有卡頓的情況下,VSync信號(hào)和RunLoop的喚醒&CADisplayLink回調(diào)的觸發(fā)嚴(yán)格一一對(duì)應(yīng)。RunLoop卡頓,無(wú)法處理Source1信號(hào),DisplayLink回調(diào)被延遲到卡頓結(jié)束時(shí)。在此過(guò)程中VSync信號(hào)間隔始終保持不變。ProMotion設(shè)備

下面看看ProMotion設(shè)備的測(cè)試結(jié)果。

VSync信號(hào)間隔可變

在ProMotion屏幕上VSync信號(hào)間隔是可變的,具體而言:

顯示靜態(tài)內(nèi)容時(shí),屏幕降頻,最低以10Hz的頻率進(jìn)行刷新顯示CoreAnimation動(dòng)畫(huà)時(shí),系統(tǒng)會(huì)適配動(dòng)畫(huà)的幀率設(shè)置改變刷新率

*通過(guò)preferredFrameRateRange可以設(shè)置hint請(qǐng)求高刷,但并不一定生效,詳見(jiàn)下文“動(dòng)態(tài)幀率的應(yīng)用場(chǎng)景”部分。

顯示滑動(dòng)中內(nèi)容時(shí),刷新率在80Hz左右波動(dòng),并且跟隨滑動(dòng)速度變化而變化。快滑時(shí)刷新率升高,慢滑時(shí)降低。顯示視頻時(shí),刷新率和視頻幀率維持一致

可以看到VSync信號(hào)間隔能主動(dòng)跟隨顯示內(nèi)容的渲染幀率的改變而改變。

減少卡頓造成的顯示延遲

在主線程發(fā)生卡頓導(dǎo)致滑動(dòng)中某一幀渲染耗時(shí)過(guò)長(zhǎng)時(shí),系統(tǒng)會(huì)改變這一幀所對(duì)應(yīng)的VSync信號(hào)間隔(下圖Surface5),減小從渲染到展示的延時(shí),從而減緩用戶(hù)感知到的卡頓時(shí)長(zhǎng)。

DisplayLink不完全跟隨VSync信號(hào)

如圖是一張滑動(dòng)中場(chǎng)景的CADisplayLink回調(diào)和Display/VSync事件對(duì)照記錄。和之前不同的是,再ProMotion設(shè)備上DisplayLink和VSync信號(hào)之間沒(méi)有表現(xiàn)出明顯的跟隨關(guān)系:

具體而言:

第三個(gè)箭頭所指向的DisplayLink的回調(diào)并不及時(shí)。在這之前主線程的卡頓已經(jīng)結(jié)束,并且額外執(zhí)行了兩次RunLoop,但直到第三次才調(diào)用了DisplayLink的回調(diào)。不僅僅是時(shí)機(jī)不匹配,也存在收到VSync但不觸發(fā)DisplayLink回調(diào)的情況(并且主線程處于空閑狀態(tài)),例如上圖中的?處。解除DisplayLink的幀數(shù)限制

我們知道,在iOS15上Apple對(duì)第三方應(yīng)用的顯示幀率默認(rèn)做了限制。第三方應(yīng)用需要在Info.plist中添加<key>CADisableMinimumFrameDurationOnPhone</key><true/>字段才可以解鎖120Hz的刷新率。

于此同時(shí),在iOS15中,CADisplayLink等動(dòng)畫(huà)相關(guān)API也新增了一個(gè)用于配置偏好幀率的屬性:

/*Definestherangeofdesiredcallbackrateinframes-per-secondforthisdisplaylink.Iftherangecontainsthesameminimumandmaximumframerate,thispropertyisidenticalaspreferredFramesPerSecond.Otherwise,theactualcallbackratewillbedynamicallyadjustedtobetteralignwithotheranimationsources.*/@property(nonatomic)CAFrameRateRangepreferredFrameRateRangeAPI_AVAILABLE(ios(15.0),watchos(8.0),tvos(15.0));

為了進(jìn)一步探究新設(shè)備上DisplayLink和VSync信號(hào)之間的關(guān)系,筆者將測(cè)試App的CoreAnimation的幀率限制解除,并配置對(duì)應(yīng)的API,分別在不同的場(chǎng)景重新進(jìn)行測(cè)試:

顯示動(dòng)態(tài)內(nèi)容的場(chǎng)景動(dòng)畫(huà)場(chǎng)景

展示一個(gè)速度中等的位移動(dòng)畫(huà),得到下圖:

可以很直觀地發(fā)現(xiàn),DisplayLink解鎖幀率后的屏幕刷新率基本穩(wěn)定在120Hz。并且VSync和DisplayLink的關(guān)系似乎又重新一一對(duì)應(yīng)了起來(lái)。

但是,將動(dòng)畫(huà)速度減慢,筆者發(fā)現(xiàn)這種對(duì)應(yīng)關(guān)系發(fā)生了變化:

可以觀察到在播放慢速動(dòng)畫(huà)時(shí),DisplayLink的頻率依然是配置的120Hz,但是實(shí)際的屏幕刷新率卻只有30Hz。

滑動(dòng)場(chǎng)景

讓我們換一種場(chǎng)景再次進(jìn)行測(cè)試,快速滑動(dòng)視圖,在Instruments中得到下圖:

可以發(fā)現(xiàn),DisplayLink解鎖幀率后,屏幕刷新率同樣基本穩(wěn)定在120Hz,僅在丟幀時(shí)有降頻。

需要注意的是筆者在CADisplayLink的回調(diào)中除了調(diào)用os_signpost上報(bào)log外無(wú)任何UI改動(dòng)。即便筆者展示的TableView極其簡(jiǎn)單,上圖中仍然可以觀察到丟幀,無(wú)法在滑動(dòng)中完美穩(wěn)定120Hz。這也許說(shuō)明UIKit的渲染性能在120Hz下會(huì)有某種程度上的原生瓶頸。

然后降低滑動(dòng)屏幕的速度,得到了和慢速動(dòng)畫(huà)相似的結(jié)果,盡管DisplayLink回調(diào)速度不減,但是VSync信號(hào)頻率一直保持在較低的水平:

卡頓場(chǎng)景

上面兩次測(cè)試都接近理想情況,即整個(gè)RenderLoop執(zhí)行幾乎沒(méi)有延遲與卡頓。但是現(xiàn)實(shí)中應(yīng)用的運(yùn)行總是有著各種各樣的或大或小的卡頓問(wèn)題。

為了驗(yàn)證更接近現(xiàn)實(shí)情況下,DisplayLink和VSync信號(hào)之間的關(guān)系,在連續(xù)滑動(dòng)的情況下筆者人為加入了一個(gè)20ms的微小卡頓進(jìn)行測(cè)試:

上圖中可以看到,ProMotion屏幕很好的處理了這次卡頓,由于三緩沖機(jī)制的存在,再RenderLoop渲染Surface4卡頓期間,通過(guò)改變VSync間隔,系統(tǒng)嘗試將緩沖區(qū)中的Surface283與Surface250延遲上屏,盡量縮短了用戶(hù)看到靜止畫(huà)面的時(shí)長(zhǎng)。

隨后,主線程恢復(fù)執(zhí)行,可以看到DisplayLink的回調(diào)頻率很快恢復(fù)至卡頓前的高水平。而此時(shí)VSync信號(hào)由于前述卡頓減緩機(jī)制的存在頻率其實(shí)有所降低。此時(shí)二者頻率并不吻合。

這和之前播放慢速動(dòng)畫(huà)/慢速滑動(dòng)的情況很相似,由于卡頓加上緩沖機(jī)制的存在導(dǎo)致短時(shí)間內(nèi)系統(tǒng)將屏幕的刷新頻率降低,但在CPU側(cè)依然維持了DisplayLink的高速回調(diào),滿足了使用方對(duì)preferredFrameRateRange這一API的設(shè)置。

為了進(jìn)一步分析了這種機(jī)制的本質(zhì),筆者接下來(lái)會(huì)嘗試逆向分析iOS15中的系統(tǒng)庫(kù)相關(guān)實(shí)現(xiàn)的改動(dòng)。

逆向分析DisplayLink驅(qū)動(dòng)方式的變化

在CADisplayLink回調(diào)***上設(shè)置斷點(diǎn),分別在iOS14和15ProMotion設(shè)備上運(yùn)行,可以得到:

在iOS14上,CADisplayLink是通過(guò)Source1mach_port直接接受VSync信號(hào)驅(qū)動(dòng)的在iOS15ProMotion設(shè)備上,CADisplayLink不再由VSync信號(hào)驅(qū)動(dòng),而是由一個(gè)UIKit內(nèi)部的Source0信號(hào)驅(qū)動(dòng)

在15中,CADisplayLink第一次創(chuàng)建并添加至RunLoop的時(shí)候,會(huì)注冊(cè)一個(gè)Source1信號(hào),這和14中行為一致。

其callout回調(diào)地址對(duì)應(yīng)符號(hào)為同樣為display_timer_callback,同樣和14中的一致。

這也可以解釋為什么15上VSync信號(hào)確實(shí)會(huì)喚醒一次RunLoop,只是這次喚醒并不一定觸發(fā)DisplayLink的回調(diào),這就說(shuō)明display_timer_callback行為和14相比一定發(fā)生了某種變化。

display_timer_callback邏輯的變化

使用Hopper分析display_timer_callback的實(shí)現(xiàn),發(fā)現(xiàn)15和14的實(shí)現(xiàn)并無(wú)區(qū)別。使用LLDB進(jìn)行debug,逐步分析,觀察到后續(xù)調(diào)用函數(shù)為CA::Display::DisplayLink::callback,其關(guān)鍵反匯編代碼如下圖所示:

觀察反匯編代碼可以發(fā)現(xiàn),如果CA::display_link_will_fire_handler這個(gè)block返回了NO,則這次VSync信號(hào)回調(diào)不會(huì)觸發(fā)后續(xù)的CA::DisplayLink::dispatch_items調(diào)用。

實(shí)際上在LLDB中也驗(yàn)證了這點(diǎn):

注意上圖中的_CFRunLoopCurrentIsMain和上圖紅框代碼接近,后續(xù)的blraa指令看起來(lái)很明顯是調(diào)用了一個(gè)block(上面的ldrx9[x8,#0x10]就是把invoke指針從block結(jié)構(gòu)體中取出的意思)。tbz指令中w0寄存器為block執(zhí)行的返回值,為0(即NO)時(shí)跳轉(zhuǎn)至0x1848dbc08,而0x1848dbc08剛好在dispatch_items的調(diào)用之后,跳過(guò)了該調(diào)用。

通過(guò)對(duì)上圖中blraa指令stepin,我們發(fā)現(xiàn)這個(gè)block實(shí)際上是由UIKitCore注冊(cè)的:

找到引用了該符號(hào)的UIKit的私有***__UIUpdateCycleSchedulerStart,反匯編結(jié)果也驗(yàn)證了這點(diǎn)。

同時(shí)發(fā)現(xiàn)這個(gè)block的返回值固定為0x0。

而同樣的symbol在之前的iOS版本上并不存在,也就是說(shuō)這個(gè)應(yīng)該是iOS15的變動(dòng)。換安裝了iOS15的非ProMotion設(shè)備,重走上面的逆向流程發(fā)現(xiàn),該設(shè)備的CA::display_link_will_fire_handler為nil,未注冊(cè):

這里cbz執(zhí)行了跳轉(zhuǎn),說(shuō)明x0為nil,而x0是由ldrx0,[x8,#0x1c8]得到。

可以看到x0就是CA::display_link_will_fire_handler。繼續(xù)分析之前找到的私有符號(hào)__UIUpdateCycleSchedulerStart的相關(guān)實(shí)現(xiàn),可以知道這是因?yàn)樵诜荘roMotion設(shè)備上_UIUpdateCycleEnabled返回了NO導(dǎo)致的。

在返回NO的情況下__UIUpdateCycleSchedulerStart***不會(huì)執(zhí)行,CA::display_link_will_fire_handler也就不會(huì)被注冊(cè)。

_UIUpdateCycleEnabled所帶來(lái)的變化

繼續(xù)研究_UIUpdateCycleEnabled相關(guān)的代碼,筆者發(fā)現(xiàn)這個(gè)的改動(dòng)并不是僅僅影響DisplayLink驅(qū)動(dòng)方式那么簡(jiǎn)單。

當(dāng)_UIUpdateCycleEnabled返回YES時(shí),UIKit會(huì)在UIApplicationMain中執(zhí)行_UIUpdateCycleSchedulerStart。分析該函數(shù),發(fā)現(xiàn)_UIUpdateCycleEnabled啟用時(shí)會(huì)調(diào)用[CATransactionsetDisableRunLoopObserverCommits:YES]。

CoreAnimation是絕大部分iOS應(yīng)用的渲染引擎,熟悉iOS渲染流程的同學(xué)想必都知道它的執(zhí)行也是由MainRunLoop驅(qū)動(dòng),大致為:

MainRunLoop因?yàn)橛脩?hù)操作/Timer/GCD等被喚醒,派發(fā)相應(yīng)的事件/回調(diào)回調(diào)中應(yīng)用修改LayerTree,觸發(fā)setNeedsLayout或setNeedsDisplayMainRunLoop即將完成本次執(zhí)行,在即將休眠前向Observer派發(fā)BeforeWaiting事件BeforeWaiting中觸發(fā)CoreAnimation注冊(cè)的MainRunLoopObserver,觸發(fā)事務(wù)提交CA::Transaction::commit():自頂向下觸發(fā)各種Layout/Display等邏輯,更新布局/內(nèi)容CoreAnimation將更新后的LayerTree打包發(fā)送給RenderServer

5.隨后MainRunLoop進(jìn)入休眠

6.RenderServer將打包好的LayerTree解碼,生成并提交對(duì)應(yīng)的drawcalls

7.GPU執(zhí)行渲染指令,渲染出FrameBuffer,待后續(xù)VSync信號(hào)來(lái)臨時(shí)上屏展示

上圖中+[CATransactionsetDisableRunLoopObserverCommits:YES]這個(gè)調(diào)用給了筆者提示,讓我們驗(yàn)證一下CA::Transaction::commit()在iOS15ProMotion設(shè)備上的執(zhí)行時(shí)機(jī),會(huì)發(fā)現(xiàn)確實(shí)不再由BeforeWaiting事件驅(qū)動(dòng)了:

實(shí)際上同樣的Source0信號(hào)同時(shí)也驅(qū)動(dòng)了CADisplayLink的回調(diào):

關(guān)注這個(gè)Source0的回調(diào)符號(hào)runloopSourceCallback,會(huì)發(fā)現(xiàn)這個(gè)Source0是由signalChanges函數(shù)驅(qū)動(dòng):

而signalChanges又是由多個(gè)回調(diào)所驅(qū)動(dòng):

其中:

runloopObserverCallback為一個(gè)BeforeWaiting的MainRunLoopobserver驅(qū)動(dòng)。runloopTimerCallback由mk_timer驅(qū)動(dòng),對(duì)應(yīng)的mach_port不明,測(cè)試發(fā)現(xiàn)其回調(diào)頻率在1Hz左右,但也會(huì)不斷變化,猜測(cè)是某種系統(tǒng)計(jì)時(shí)器。inputGroupSignaledCallback由mk_timer驅(qū)動(dòng),對(duì)應(yīng)的mach_port正是VSync信號(hào)。

4.requestRegistrySignaledCallback由UIScrollView在即將開(kāi)始滑動(dòng)時(shí)驅(qū)動(dòng)。

通過(guò)上面的分析,筆者有理由認(rèn)為在iOS15上應(yīng)用的渲染驅(qū)動(dòng)機(jī)制出現(xiàn)了比較大的變化。其中之一便是DisplayLink的驅(qū)動(dòng)源的改變。

結(jié)論iOS15上Apple改變了在ProMotion設(shè)備的渲染事件循環(huán)的驅(qū)動(dòng)方式,CoreAnimation的事務(wù)提交不再由完全由RunLoop驅(qū)動(dòng),而是涉及了多個(gè)信號(hào)源系統(tǒng)動(dòng)態(tài)幀率選擇的機(jī)制會(huì)綜合考慮使用方設(shè)置的API(如preferredFrameRateRange)和實(shí)際展示的內(nèi)容的變化頻率。具體對(duì)CADisplayLink而言:內(nèi)容低速變化時(shí),CADisplayLink解鎖高刷新率僅影響自身的回調(diào)頻率,系統(tǒng)仍可能選擇較低的屏幕刷新率來(lái)降低功耗內(nèi)容中高速變化時(shí),CADisplayLink解鎖高刷新率可以讓系統(tǒng)選擇更高的刷新頻率,甚至實(shí)現(xiàn)鎖定120Hz的刷新

關(guān)于如何界定低速/中高速,筆者在下文中CAAnimation設(shè)置動(dòng)態(tài)幀率部分做了一些試驗(yàn),可作為參考。

同時(shí),默認(rèn)配置的CADisplayLink回調(diào)頻率最高為60Hz,無(wú)法監(jiān)控更高頻率的刷新事件。

3.ProMotion設(shè)備中,DisplayLink不再由VSync信號(hào)直接驅(qū)動(dòng),而是在新引入的渲染事件循環(huán)中執(zhí)行。新版本iOS系統(tǒng)實(shí)現(xiàn)了某種更復(fù)雜的機(jī)制來(lái)盡可能滿足使用者設(shè)置的偏好頻率進(jìn)行回調(diào),但并不保證它與VSync信號(hào)的強(qiáng)關(guān)聯(lián)性。這意味著默認(rèn)的CADisplayLink的回調(diào)頻率與實(shí)際幀率并不匹配,之前基于CADisplayLink進(jìn)行幀率監(jiān)控的方案在ProMotion設(shè)備上變得不再可行。

動(dòng)態(tài)幀率的應(yīng)用場(chǎng)景監(jiān)控動(dòng)態(tài)幀率下的流暢度表現(xiàn)

業(yè)界中一般采用CADisplayLink對(duì)應(yīng)用的流暢度進(jìn)行監(jiān)控。由于CADisplayLink的行為在iOS15上的變化,原先的監(jiān)控方案無(wú)法評(píng)估ProMotion屏幕在超過(guò)60Hz時(shí)的表現(xiàn)。

根據(jù)上面的探索結(jié)論,目前筆者設(shè)想了三種針對(duì)ProMotion設(shè)備的兼容性修改方案:

方案一[Pass]

對(duì)于任何設(shè)備都以60Hz為優(yōu)化目標(biāo),只考慮刷新間隔長(zhǎng)于16.67ms的情況。換句話說(shuō),在屏幕以120Hz刷新時(shí),對(duì)于丟1幀的情況也認(rèn)為不丟幀,因?yàn)榇藭r(shí)兩幀之間的間隔仍然小于16.67ms,理論上用戶(hù)感知不大。

優(yōu)點(diǎn):

方案簡(jiǎn)單,僅需設(shè)置preferredFramesPerSecond為固定值60即可兼容之前的指標(biāo)。依然可以計(jì)算FPS指標(biāo),對(duì)于刷新率高于60Hz的情況統(tǒng)一認(rèn)為刷新率為60Hz

缺點(diǎn):

由于只能監(jiān)控最高60Hz的情況,無(wú)法評(píng)估更高刷新率下一些微小丟幀對(duì)用戶(hù)體驗(yàn)帶來(lái)的影響,也無(wú)法評(píng)估對(duì)高刷屏的一些優(yōu)化所帶來(lái)的技術(shù)影響在低刷新率時(shí),MainRunLoop依然會(huì)以60Hz運(yùn)行,對(duì)功耗有一定影響方案二[Pass]

通過(guò)一些手段,可以替換驅(qū)動(dòng)display_timer_callback的Source1信號(hào)的回調(diào),使用它來(lái)準(zhǔn)確監(jiān)聽(tīng)VSync信號(hào),實(shí)現(xiàn)對(duì)動(dòng)態(tài)幀率的準(zhǔn)確監(jiān)控。

優(yōu)點(diǎn):

理論上最精確的監(jiān)控方案對(duì)功耗的影響最小,回調(diào)頻率只有在屏幕刷新率實(shí)際升高時(shí)才會(huì)隨之提升

缺點(diǎn):

使用了私有APIFPS指標(biāo)從此不再適用VSync信號(hào)目前和渲染流程不完全匹配,雖然精確但不一定實(shí)用方案三[Pick]

通過(guò)在CADisplayLink回調(diào)中確認(rèn)duration參數(shù),計(jì)算得到當(dāng)前屏幕的實(shí)時(shí)刷新率,并修改preferredFrameRateRange來(lái)進(jìn)行跟蹤。

優(yōu)點(diǎn):

方案相對(duì)簡(jiǎn)單,只需在每次回調(diào)中更新DisplayLink對(duì)象的preferredFrameRateRange屬性即可

缺點(diǎn):

由于動(dòng)態(tài)幀率的存在,F(xiàn)PS指標(biāo)可以反映實(shí)時(shí)屏幕刷新情況,但是聚合后的意義不大,消費(fèi)時(shí)需要區(qū)分特定機(jī)型/場(chǎng)景觀察到目前的最小回調(diào)頻率為60Hz,也就是說(shuō)無(wú)法確認(rèn)ProMotion屏幕在48Hz、30Hz甚至更低刷新率下的表現(xiàn)在低刷新率時(shí),MainRunLoop依然會(huì)以60Hz運(yùn)行,對(duì)功耗有一定影響

需要注意的是,CADisplayLink的preferredFrameRateRange需要以類(lèi)似一下格式進(jìn)行設(shè)置:

NSIntegercurrentFPS=(NSInteger)ceil(1.0/displayLink.duration);displayLink.preferredFrameRateRange=CAFrameRateRangeMake(10.0,currentFPS,0.0);

CAFrameRateRange.minimum傳最小值10.0,preferred傳0.0,可以讓該CADisplayLink只用于監(jiān)控當(dāng)前的系統(tǒng)幀率,而不影響幀率的動(dòng)態(tài)選擇。

相比前兩個(gè)方案,方案三改動(dòng)小,不使用私有API,監(jiān)控準(zhǔn)確性也較高,缺點(diǎn)相對(duì)來(lái)說(shuō)可以接受。

FPS的替代指標(biāo)

考慮到在ProMotion屏幕上FPS指標(biāo)不再與應(yīng)用運(yùn)行是否流暢直接相關(guān),它的聚合值參考價(jià)值不大,有必要尋找一個(gè)新指標(biāo)作為替換。

Apple官方在WWDC20-10077EliminateanimationhitcheswithXCTest中介紹了HitchTimeRatio這一概念,并著重說(shuō)明了它比單純的FPS更能適配不同刷新率的場(chǎng)景。

在XCTest框架中,蘋(píng)果提供了APIXCTOSSignpostMetric幫助開(kāi)發(fā)者在單測(cè)中即時(shí)地獲取該指標(biāo),但相關(guān)API盡在單測(cè)中提供,線上無(wú)法使用。而MetricKit中的MXAnimationMetric盡管可以在線上獲取,但卻不是實(shí)時(shí)的,無(wú)法滿足大型App對(duì)不同場(chǎng)景的監(jiān)控需求。

因此,遵循下面Apple對(duì)HitchRatio的定義:

Hitchtime:

Timeinmsthataframeislatetodisplay.

Hitchtimeratio:

Hitchtimeinmspersecondforagivenduration.

筆者嘗試實(shí)現(xiàn)了基于CADisplayLink的(Scroll)HitchTimeRatio的計(jì)算方案:

計(jì)算上一幀的幀時(shí)間戳與上上一幀的目標(biāo)幀時(shí)間戳得到上一幀的HitchTime確定該幀是否是在滑動(dòng)中渲染累計(jì)得到整體的HitchFrame,與累積的幀間隔相比,得到(Scroll)HitchTimeRatio關(guān)鍵場(chǎng)景提升幀率

在測(cè)試過(guò)程中筆者發(fā)現(xiàn),系統(tǒng)App滑動(dòng)時(shí)是穩(wěn)定以最高刷新率120Hz運(yùn)行的:

而第三方App即便設(shè)置了CADisableMinimumFrameDurationOnPhone為true也無(wú)法穩(wěn)定以滿幀率滑動(dòng)(經(jīng)過(guò)驗(yàn)證,這一點(diǎn)在iOS15.4beta系統(tǒng)上依然成立)。

通過(guò)利用iOS15引入的新API,我們可以在關(guān)鍵場(chǎng)景如滑動(dòng)、轉(zhuǎn)場(chǎng)、動(dòng)畫(huà)過(guò)程中主動(dòng)解鎖更高/限制更低的動(dòng)態(tài)幀率,從而優(yōu)化流暢度或者優(yōu)化功率,提升用戶(hù)體驗(yàn)?zāi)繕?biāo)。

滑動(dòng)中穩(wěn)定120Hz

首先,筆者希望非系統(tǒng)App也可以盡可能實(shí)現(xiàn)滑動(dòng)中穩(wěn)定120Hz刷新。

結(jié)合上述分析,這一點(diǎn)可以用CADisplayLink來(lái)實(shí)現(xiàn)。這里筆者提出兩種可能方案僅供參考:

創(chuàng)建CADisplayLink,配置其preferredFramesPerSecond為120,然后將其添加到UITrackingRunLoopMode中。CADisplayLink*dp=...dp.preferredFramesPerSecond=120;//或者dp.preferredFrameRateRange=CAFrameRateRangeMake(120.0,120.0,0.0);[dpaddToRunLoop:[NSRunLoopmainRunLoop]forMode:UITrackingRunLoopMode];

在滑動(dòng)中,該CADisplayLink被激活,系統(tǒng)鎖定當(dāng)前幀率為最高120Hz(僅在內(nèi)容中高速變化時(shí)生效)。停止滑動(dòng)時(shí)則恢復(fù)正常幀率。

添加CADisplayLink至CommonModes中,分別在開(kāi)始/停止滑動(dòng)時(shí)啟用/暫停CADisplayLink,并修改對(duì)應(yīng)的preferredFramesPerSecond等屬性,觸發(fā)幀率變化。CADisplayLink*dp=...dp.paused=YES;[dpaddToRunLoop:[NSRunLoopmainRunLoop]forMode:NSRunLoopCommonModes];CFRunLoopAddObserver(CFRunLoopGetMain(),CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault,kCFRunLoopEntry|kCFRunLoopExit,YES,0,^(CFRunLoopObserverRefobserver,CFRunLoopActivityactivity){if(activity==kCFRunLoopEntry){dp.paused=NO;dp.preferredFramePerSecond=120;}else{dp.paused=YES;dp.preferredFramePerSecond=0;}}),(__bridgeCFStringRef)UITrackingRunLoopMode);

在實(shí)踐中,由于也存在需要在非滑動(dòng)狀態(tài)下解鎖幀率上限的情況,所以方案2的通用性會(huì)更好。

CAAnimation設(shè)置動(dòng)態(tài)幀率

目前蘋(píng)果只提供了修改CAAnimation動(dòng)畫(huà)幀率的API,設(shè)置CAAnimation.preferredFrameRateRange即可改變其對(duì)屏幕刷新率的影響。

對(duì)于用戶(hù)感知明顯的,如轉(zhuǎn)場(chǎng)動(dòng)畫(huà),可以設(shè)置為120Hz。對(duì)于感知不明顯的,如旋轉(zhuǎn)動(dòng)畫(huà),可以降低其幀率,比如設(shè)置為30Hz。

但是,和DisplayLink相同,過(guò)上述API的設(shè)置雖然會(huì)“影響”系統(tǒng)的動(dòng)態(tài)幀率的選擇,但這種影響并不是絕對(duì)的。在實(shí)際使用中,筆者發(fā)現(xiàn)屏幕選擇的刷新率和CAAnimation在屏幕上變化的速度有關(guān)。

關(guān)于此點(diǎn),以iPhone13Pro為例,筆者使用了一個(gè)簡(jiǎn)單的、偏好幀率為固定120Hz平移動(dòng)畫(huà)進(jìn)行說(shuō)明:

CABasicAnimation*anim=[CABasicAnimationanimationWithKeyPath:@"transform.translation.y"];CGFloatspeed=170.0/330.0;anim.toValue=@(100);anim.fromValue=@(0);anim.duration=10.0;anim.repeatCount=FLT_MAX;anim.preferredFrameRateRange=CAFrameRateRangeMake(120,120,120);

其中speed變量為平移的速度,單位為pt/s,試驗(yàn)發(fā)現(xiàn):

speed取(0,160]時(shí),屏幕刷新率為60Hzspeed取[161,320]時(shí),屏幕刷新率為80Hzspeed取[321,+∞)時(shí),屏幕刷新率為120Hz

筆者僅在iPhone13Pro上測(cè)試了平移動(dòng)畫(huà)的場(chǎng)景,以上數(shù)據(jù)僅供參考。

最后,對(duì)于其他的常見(jiàn)的動(dòng)畫(huà)API,例如UIView.animateWithDuration、UIViewPropertyAnimator等,則沒(méi)有提供對(duì)應(yīng)API進(jìn)行修改。理論上也可以通過(guò)某些手段拿到這些上層API所創(chuàng)建的CAAnimation對(duì)象來(lái)實(shí)現(xiàn)修改。

手勢(shì)/轉(zhuǎn)場(chǎng)等其他場(chǎng)景解鎖120Hz

其他場(chǎng)景需要控制動(dòng)態(tài)幀率的也可以通過(guò)手動(dòng)修改CADisplayLink的preferredFramePerSecond/preferredFrameRateRange屬性來(lái)實(shí)現(xiàn),其實(shí)現(xiàn)和通過(guò)監(jiān)聽(tīng)RunLoop來(lái)修改滑動(dòng)幀率基本相同。

UIGestureRecognizer常被用于實(shí)現(xiàn)的交互式動(dòng)畫(huà)。經(jīng)過(guò)測(cè)試,發(fā)現(xiàn)在觸發(fā)手勢(shì)回調(diào)的同時(shí)啟用一個(gè)解鎖了頻率的CADisplayLink也可以間接提高UIGestureRecognizer的回調(diào)頻率,從而實(shí)現(xiàn)更高幀率的交互動(dòng)畫(huà)。

對(duì)于轉(zhuǎn)場(chǎng)的場(chǎng)景,一個(gè)簡(jiǎn)單的方案是swizzleUIViewController的生命周期消息,在出現(xiàn)/消失的節(jié)點(diǎn)啟用/停用CADisplayLink幀率的解鎖,從而實(shí)現(xiàn)通用的頁(yè)面轉(zhuǎn)場(chǎng)動(dòng)畫(huà)幀率解鎖方案。

Flutter官方也計(jì)劃提供類(lèi)似API讓?xiě)?yīng)用側(cè)可以針對(duì)不同的場(chǎng)景(滑動(dòng)、動(dòng)畫(huà)etc)動(dòng)態(tài)切換屏幕刷新率:https://github.com/flutter/flutter/issues/90675

上線收益

基于上述思路,筆者所在團(tuán)隊(duì)在國(guó)際化短視頻業(yè)務(wù)落地了優(yōu)化項(xiàng)目,經(jīng)過(guò)實(shí)驗(yàn)驗(yàn)證:

大盤(pán)滑動(dòng)幀率P50從81.57上升至112.2核心業(yè)務(wù)指標(biāo)也有一定收益結(jié)語(yǔ)

近年來(lái),Apple生態(tài)中軟硬件的發(fā)展日新月異,有軟件層的dyld的持續(xù)優(yōu)化和iOS15新引入的Prewarm機(jī)制,也有新的ProMotion屏幕,可以看到Apple一直致力于打造更絲滑流暢的用戶(hù)體驗(yàn)。

Apple提供的系統(tǒng)級(jí)優(yōu)化方案一般通用而無(wú)感知,但通用往往也意味著一定的局限性,可能預(yù)留了額外優(yōu)化空間,應(yīng)用開(kāi)發(fā)者們可以進(jìn)一步去研究如何更好地適配。

例如本文中,筆者通過(guò)研究新引入的ProMotion屏幕背后的機(jī)制,透過(guò)表象/深入?yún)R編管中窺豹看到一部分本質(zhì),最終落地了監(jiān)控+優(yōu)化的方案,讓大盤(pán)滑動(dòng)幀率P50從80上升至112左右,取得了額外的業(yè)務(wù)收益。

最后,筆者認(rèn)為,我們普通開(kāi)發(fā)者作為Apple生態(tài)鏈中的一環(huán),在享受系統(tǒng)級(jí)別優(yōu)化自動(dòng)帶來(lái)的收益的同時(shí),也應(yīng)該主動(dòng)去了解上述優(yōu)化背后的底層原理。一方面,了解與學(xué)習(xí)Apple的成熟優(yōu)化思路可以提升我們作為工程師的眼界。另一方面,對(duì)系統(tǒng)底層原理的了解可以拓充我們的“彈藥庫(kù)”,對(duì)業(yè)務(wù)價(jià)值交付的全鏈路了解越廣越深,越有可能抓住潛在的優(yōu)化點(diǎn),從而在性能優(yōu)化工程師這條職業(yè)道路上走得更遠(yuǎn)更好。

參考資料WWDC20-10077EliminateanimationhitcheswithXCTest

https://developer.apple.com/videos/play/wwdc2020/10077

WWDC21-10147Optimizeforvariablerefreshratedisplays

https://developer.apple.com/videos/play/wwdc2021/10147/

OptimizingProMotionRefreshRatesforiPhone13ProandiPadPro

https://developer.apple.com/documentation/quartzcore/optimizing_promotion_refresh_rates_for_iphone_13_pro_and_ipad_pro?language=objc

WhatisAdaptiveSync?

https://www.viewsonic.com/library/tech/explained/what-is-adaptive-sync/

https://github.com/flutter/flutter/issues/90675加入我們

我們是字節(jié)國(guó)際化短視頻基礎(chǔ)技術(shù)團(tuán)隊(duì),是一個(gè)深度追求極致的團(tuán)隊(duì),我們專(zhuān)注于性能、架構(gòu)、包大小、穩(wěn)定性、自動(dòng)化測(cè)試、基礎(chǔ)庫(kù)、編譯構(gòu)建等方向的深耕,保障超大規(guī)模團(tuán)隊(duì)的研發(fā)效率和全球數(shù)億用戶(hù)的使用體驗(yàn)。目前上海、杭州、新加坡、美國(guó)都有大量人才需要,歡迎有志之士與我們共同建設(shè)億級(jí)用戶(hù)全球化APP!

可以點(diǎn)擊「鏈接」,進(jìn)入字節(jié)跳動(dòng)招聘官網(wǎng)投遞簡(jiǎn)歷,也可以郵件聯(lián)系:kazec.liu@bytedance.com咨詢(xún)相關(guān)信息或者直接發(fā)送簡(jiǎn)歷內(nèi)推!

猜你喜歡

主站蜘蛛池模板: aaaa欧美高清免费 | 亚洲精品国产精品国自产 | 国产人成免费视频 | 国产年成美女网站视频免费看 | 欧美真人毛片动作视频 | 欧美人成在线观看ccc36 | 成人做爰视频www片 成人做爰视频www视频 | 精品一区二区高清在线观看 | 国产成人午夜极速观看 | 一区中文字幕 | 久久91精品国产91久久户 | www久久久| 欧美性一级 | 久久亚洲综合中文字幕 | 一级毛片国产 | 国产亚洲欧美一区二区 | 国产精品久久久久久久久久一区 | 久久久不卡国产精品一区二区 | 国产4tube在线播放 | 国产在线视频专区 | 久久男人的天堂色偷偷 | 国产成人亚洲精品无广告 | 亚洲va中文字幕 | 俄罗斯小屁孩cao大人免费 | 性欧美成人依依影院 | 亚洲午夜精品一级在线 | 亚洲高清一区二区三区 | 久久精品久久精品久久精品 | 一级做a爰片性色毛片视频图片 | 中国一级毛片欧美一级毛片 | 国产精品亚洲综合久久 | 国产三级在线观看 | aaa在线观看高清免费 | 日韩欧国产精品一区综合无码 | 伊大人香蕉久久网欧美 | 黄色大片三级 | 久久久久久久久久免费视频 | 成人 在线播放 | 国内精品久久久久久影院老狼 | 麻豆影音 | 请看一下欧美一级毛片 |