常说的手机刷新率60Hz、120Hz有什么不同?
在很長一段時間里,手機的刷新率都是60Hz,隨著硬件設備性能的提升,各種高刷新率的移動設備層出不窮,移動端也能有120Hz的顯示設備。那么手機上的游戲真的是FPS越高越好嗎?本期我們就來探索這其中的真相。文章作者:侯鑫,騰訊游戲引擎研發工程師。
背景
作為手機游戲開發者,我們的工作中有很多時間都在嘗試優化自己的代碼。比如讓某一段邏輯執行的更快速,或降低一些迭代的頻率,減輕CPU負擔,抑或通過各種騷操作在不降低畫面質量的情況下,減輕GPU負擔。
最終的目的都是想讓使用不同性能設備的玩家都可以流暢的體驗游戲。“卡不卡”也是玩家對游戲產生的第一印象之一,因此,我們的目的就是讓游戲以最快的速度運行。
通常,評價一款游戲是否流暢的最直觀的指標就是FPS(幀/秒)。那么,FPS真的是越高越好嗎?
市面上評測硬件性能都是對比不同硬件下相同畫質游戲的FPS,且越高越好(Higher is better)。
對于桌面平臺來講,它有持續穩定的供電與強勁的散熱方案,不需要考慮發熱與耗電量的問題,可以讓硬件自由發揮。其次我們還要考慮顯示設備(手機屏幕、電腦屏幕)的刷新率(RefreshRate)。
為了了解幀率(單位是FPS)與刷新率的關系,我們先來看看他們的定義:
1. 幀率(FrameRate)
幀率是GPU和CPU合作在游戲運行時,可產生的圖像的數量,計量單位是幀/秒(FramePerSecond),通常是評估硬件性能與游戲體驗流暢度的指標。
2. 刷新率(RefreshRate)
刷新率(垂直刷新率、垂直掃描率)表示顯示設備一秒內可顯示的新圖像的數量,計量單位是赫茲(Hz)。
刷新率與幀率是兩個獨立的概念,幀率表示驅動顯示器的設備每秒可產生新圖像的數量。
可簡單理解為:
游戲引擎與驅動是生產者,工作效率用幀率來評價;
顯示設備是消費者,工作效率用刷新率來評價。
簡而言之,我們真正感受到的流暢度會被刷新率限制,當幀率高過刷新率時,顯示器每秒所能顯示的圖像數量仍然是不變的。
3. 畫面撕裂(ScreenTearing)
假設你的顯示設備刷新率為60Hz,當幀率高過刷新率或游戲運行時的幀時間不是1/60的倍數(2/60、3/60),即其FPS不是:…/120/60/30/20/… 時,就會存在顯示器正在刷新圖像的同時,新的數據也正由顯卡傳過來的情況,導致屏幕中有多幀數據同時出現的情況。
如上圖所示,B幀渲染較快,在A幀的數據仍在顯示器中刷新時,提交了新數據,造成畫面撕裂,這種現象就叫做畫面撕裂。這種瑕疵最簡單的解決方案是垂直同步(VSync)。
4. 垂直同步(Vertical Synchronization)
垂直同步會同步顯卡與顯示設備的工作:
當顯示器在刷新數據時,會讓GPU等待,直到完全刷新數據后,讓GPU提交新的數據,并在下一個刷新周期刷新。
垂直同步會將游戲的FPS限制為顯示設備的刷新率,其最大的問題是會導致玩家輸入延遲,因為它會要求顯卡在渲染完畢后等待顯示設備去刷新數據。
顯而易見,這個問題對于競技游戲的影響是很大的。
(1)NVIDIA G-Sync
如果你是NVIDIA顯卡,且你的顯卡和顯示器均支持G-Sync[1],則顯示器中的特殊芯片會與顯卡進行交流,使顯示器調整其刷新率以匹配顯卡的幀率。
其缺點是需要硬件(顯示設備硬件)支持。
(2)AMD FreeSync
FreeSync[2]是基于DP接口(DisplayPort)所支持的自動同步技術[3](Adaptive-Sync)實現的。
這是一個開放的技術標準,因此FreeSync不需要授權費用,通常支持FreeSync的顯示設備會比G-Sync便宜。
了解到影響用戶體驗的這些因素后,我們也了解到相應的解決方案。那么,移動平臺的具體情況是什么?與桌面平臺又有什么不同呢?
移動平臺
以高通為例,其Adreno[4] GPU的性能數據如下表所示:
高通驍龍865所配置的Adreno 650與Intel 11代CPU所附帶的Iris Plus集顯對比:
可以看到目前的旗艦移動GPU的浮點數算力已經趕超Intel的集顯,性能已經非常可觀,移動端硬件也有能力去輸出非常高的幀率。
1. Android
移動平臺的顯示設備在很長一段時間里,都是60Hz。
我們從上文了解到,在游戲圖像展示在顯示屏的過程中,有一個比較影響用戶體驗的同步過程。
游戲邏輯和渲染循環與安卓系統和顯示屏硬件之間有一個同步的關系,這個同步過程我們稱為幀節奏(Frame Pacing),也即引擎與CPU、GPU配合產生圖像的幀率 與顯示屏刷新率之間的同步關系。
安卓的顯示系統可避免畫面撕裂(ScreenTearing)的問題,即當顯示器正在刷新數據時,新的數據被Push到顯示設備時的情況。其通過以下措施避免撕裂(Tearing):
將歷史幀數據緩存住;
自動檢測有延遲的幀數據提交;
當提交有延遲時,重復渲染歷史幀數據。
通過Buffer緩存幀數據,當顯示器刷新時,如果有新數據傳輸,直接將其緩存即可。如此設計,就不會有VSYNC的阻塞式等待的問題,也不會增大影響游戲邏輯的輸入延遲。
雖然帶來了一定的畫面延遲,但可以避免畫面撕裂問題。具體的數據提交流程如下:
首先引擎通過 eglSwapBuffers 告知顯示系統 SurfaceFlinger[5],其次SurfaceFlinger 會將數據緩存到Buffer中。
如果當前是刷新的窗口期,SF會等待硬件的VSYNC信號。收到信號后,SF將從Buffer中找到最新的Buffer;如果沒有找到,就用上一次的Buffer;如果正在刷新中,SF則是處在睡眠狀態(設備實現相關)。
假設同步(Frame Pacing)頻率為30Hz時,正確的同步關系如下圖:
NB即為No Buffer,Latch即表示為Buffer傳輸中。圖中的Display刷新率為60Hz,渲染的頻率為30Hz。
(1)短幀卡頓
當某幀的渲染時間變小,會出現卡頓現象(Stuttering):
如上圖所示,C幀的渲染因為一些原因所花費的時間很短,在下一個刷新窗口期就渲染完畢了,因此曾經的NB位置存儲了C幀的圖像數據,最終導致刷新出的幀序列變為:AABCCC,C幀的FrameTime短了,反而讓玩家感受到游戲不流暢了。
(2)解決短幀卡頓
安卓提供了Swappy Frame Pacing庫(Android Game SDK[6]的一部分),UE4.25[7]與Unity2019.2[8]已合入Swappy庫。
通過 EGL_ANDROID_presentation_time[9]設置顯示器Present的時間。在我們的例子中,更新頻率是30Hz,通過設置PresentTime為30Hz,即可避免短幀卡頓的情況。
(3)長幀卡頓/延遲
如上圖,B幀因為一些原因占用了超過33.3ms的幀時間,導致NB的幀重復了兩次,造成AAABCC的幀序列,從而導致卡頓:
A延續了三幀;
B只展示了一幀。
(4)解決長幀卡頓
Swappy會通過添加同步鎖,使顯示系統有充足的等待空間,從而不至于將影響擴散。
通過同步鎖 EGL_KHR_fence_sync[10] ,雖然幀A的問題無法解決,但幀A之后的B、C都不會受到幀A的影響。
2. Frame Pacing Library
Android Game SDK 中的 Swappy 庫,不僅可以解決長短幀的問題,也可以支持動態調整設備的刷新率,以提供給玩家最流暢的視覺體驗。不同刷新率的設備支持不同的FPS:
60Hz:60FPS/30FPS/20FPS
60 + 90Hz:90FPS/60FPS/45FPS/30FPS
60 + 90 + 120Hz:120FPS/90FPS/60FPS/45FPS/40FPS/30FPS
Swappy可根據渲染器的具體幀時間,選擇最符合的刷新率,提供給玩家一個更流暢的視覺體驗,通過systrace[11]可根據SurfaceView的數據驗證Frame Pacing庫的改進。
至此我們了解到安卓平臺的Frame Pacing改進方案Swappy庫,其實就是一個簡化版的G-Sync或Free-Sync,都可以通過動態調整顯示器的刷新率(支持動態刷新率的設備)來輸出更流暢的效果
3. iOS
蘋果在2018年的WWDC上分享過一個演講[12],其中介紹了蘋果在Frame Pacing上所做的改進。
上面的動圖中雖然左側是40FPS,高于右側的30FPS,但用戶體驗明顯是30FPS側更友好。
40FPS的執行時序情況如上圖所示,VSYNC的最小間隔即刷新率為60Hz。當我們以盡可能快的速度去渲染新的幀時,0/1刷新點Display的緩存中沒有數據,因此均使用歷史數據。
即A展示了2幀。第2幀時,B幀的GPU計算完畢,可直接展示B。第3幀時,C幀的GPU計算完畢,直接展示C,且因為A的GPU錯過了刷新點4,因此C也展示了兩幀。依次循環往復,造成了:AABCCABBCAA 的長短幀問題,最終導致卡頓的表現。
(1)設置固定幀率
iOS 10.3以上支持新的API:
MTLDrawable addPresentedHandlerMTLCommandBuffer presentDrawable afterMinimumDurationMTLCommandBuffer presentDrawable atTime可設置最小幀間隔,從而解決長短幀的問題:
// Render Scene...// Get drawable and present at 30 FPSlet drawable = view.currentDrawable { // Render Final Pass ... let duration = 33.0 / 1000.0 // Duration of 33 ms commandBuffer.present(drawable, afterMinimumDuration: duration)}commandBuffer.commit()通過設置幀渲染最小間隔,可讓幀以固定的頻率渲染新的幀,從而為CPU、GPU留下了足夠長的時間去渲染場景。
假設刷新率為60Hz,只要CPU與GPU完成協作輸出數據的時間在3*(1/60)ms之內,即第1幀GPU的工作C 保證在 第3幀的工作A開啟之前完成,iOS設備就可以輸出連續的30Hz的圖像。
4. Unreal Engine 4
從4.25版本開始,UE4整合了安卓的Swappy庫:
// Runtime/OpenGLDrv/Private/Android/AndroidOpenGLFramePacer.cppvoid FAndroidOpenGLFramePacer::Init() { InitSwappy();}void FAndroidOpenGLFramePacer::InitSwappy() { JNIEnv* Env = FAndroidApplication::GetJavaEnv(); SwappyGL_init(Env, FJavaWrapper::GameActivityThis);}開啟Swappy時直接使用其API對Frame Pacing進行控制:
// r.setframepace 30// a.UseSwappyForFramePacing=1bool FAndroidOpenGLFramePacer::SwapBuffers(bool bLockToVsync) {#if USE_ANDROID_OPENGL_SWAPPY int64 DesiredFrameNS = (1000000000L) / (int64)FAndroidPlatformRHIFramePacer::GetFramePace(); SwappyGL_setSwapIntervalNS(DesiredFrameNS); SwappyGL_setAutoSwapInterval(false); SwappyGL_swap(eglDisplay, eglSurface);#endif根據UE4的代碼,UE4并未使用Swappy的默認模式[13],而是根據配置,通過Swappy設置了正確的同步節奏。
Swappy比UE4默認的FramePacer更了解安卓系統。根據UE4的文檔,其真實表現也比默認的Pacer更穩定,未來的版本也將會在安卓平臺把Swappy作為默認的FramePacer。
4.25以下版本使用UE4的Leagcy Frame Pacer:通過eglSwapInterval[14]控制Swap Buffer時所需等待的VBLANK[15]次數。
VBLANK指一幀數據最后一行顯示完畢到下一幀第一行數據開始顯示的過程,eglSwapInterval 實際上是無法精確了解顯示屏(硬件)刷新的時間的,因此其真實效果不如更了解硬件的Swappy好。
// rhi.SyncInterval, 60Hz設置為1int32 SyncInterval = GetLegacySyncInterval();// 當配置的同步間隔改變時,使用eglSwapInterval配置VBLANK等待次數if (DesiredSyncIntervalRelativeTo60Hz != SyncInterval) { eglSwapInterval(eglDisplay, DriverSyncIntervalRelativeToDevice);}若設備支持?ANDROID_get_frame_timestamps[16]?擴展,可通過API拿到驅動層的一些時間數據,計算出更精確的SwapInterval:
EGLint Item = EGL_COMPOSITE_INTERVAL_ANDROID;// The time delta between subsequent composition events.eglGetCompositorTimingANDROID_p(eglDisplay, eglSurface, 1, &Item, &COMPOSITE_INTERVAL);if (COMPOSITE_INTERVAL >= 4000000 && COMPOSITE_INTERVAL <= 41666666) { DriverRefreshRate = float(1000000000.0 / double(COMPOSITE_INTERVAL)); DriverRefreshNanos = COMPOSITE_INTERVAL;}并且可以解決短幀卡頓的問題。即通過查詢歷史幀的數據,控制Compositor的工作時機,當短幀發生時,根據刷新率計算出正確的工作時間。
保證短幀的數據B在顯示器刷新兩次,以保持體驗的流暢性:
EGLint TimestampList = EGL_FIRST_COMPOSITION_START_TIME_ANDROID;// The first time at which// the compositor began preparing composition for this frame.EGLnsecsANDROID Result = 0;eglGetFrameTimestampsANDROID_p(eglDisplay, eglSurface, FrameIDs[Index % NUM_FRAMES_TO_MONITOR], 1, &TimestampList, &Result);// 設置下一幀的起始時間EGLnsecsANDROID DeltaNanos = EGLnsecsANDROID(DesiredSyncIntervalRelativeToDevice) * EGLnsecsANDROID(DeltaFrameIndex) * DriverRefreshNanos;EGLnsecsANDROID PresentationTime = Result + DeltaNanos;eglPresentationTimeANDROID_p(eglDisplay, eglSurface, PresentationTime);(1)iOS
iOS通過控制CADisplayLink[17]的參數來控制FramePacing的表現:
FIOSPlatformRHIFramePacer::FrameInterval = NewFrameInterval;uint32 MaxRefreshRate = FIOSPlatformRHIFramePacer::GetMaxRefreshRate();CADisplayLink* displayLinkParam = (CADisplayLink*)param;// iOS 10displayLinkParam.preferredFramesPerSecond = MaxRefreshRate / FIOSPlatformRHIFramePacer::FrameInterval;// pre iOS 10displayLinkParam.frameInterval = FIOSPlatformRHIFramePacer::FrameInterval;即可通過歷史幀的數據動態調整FrameInterval或期望FPS,以達到更流暢的視覺體驗。
5. Unity
Unity2019.2之后在安卓平臺整合了Swappy作為FramePacer。
Unity2018版本僅設置了glSwapInterval,即通過不是很精確的timestamp模式控制FramePacing:
// Runtime/GfxDevice/egl/WindowContextEGL.cppEGLint WindowContextEGL::SetVSyncInterval(EGLint interval) { interval = clamp(interval, m_VSyncIntervalMin, m_VSyncIntervalMax); if (eglSwapInterval(m_EGLDisplay, interval)) return interval; return -1;}而iOS上FramePacing的實現與UE4基本一致。
參考文獻:
[1] G-Sync:
https://www.nvidia.com/en-us/geforce/products/g-sync-monitors/
[2] FreeSync:
https://www.amd.com/en/technologies/free-sync
[3] 自動同步技術:
https://vesa.org/featured-articles/vesa-adds-adaptive-sync-to-popular-displayport-video-standard/
[4] Adreno :
https://en.wikipedia.org/wiki/Adreno
[5] ?SurfaceFlinger:
https://source.android.com/devices/graphics/surfaceflinger-windowmanager
[6] Android Game SDK:
https://developer.android.com/games/sdk
[7] UE4.25:
https://docs.unrealengine.com/en-US/Platforms/Mobile/Rendering/MobileFramePacing/index.html
[8] Unity2019.2:
https://unity3d.com/unity/alpha/2019.2.0a6
[9] EGL_ANDROID_presentation_time:
https://www.khronos.org/registry/EGL/extensions/ANDROID/EGL_ANDROID_presentation_time.txt
[10] ?EGL_KHR_fence_sync:
https://www.khronos.org/registry/EGL/extensions/KHR/EGL_KHR_fence_sync.txt
[11] systrace:
https://developer.android.com/games/sdk/frame-pacing/opengl/verify-improvement
[12] WWDC演講:
https://developer.apple.com/videos/play/wwdc2018/612/
[13] Swappy默認模式:
https://developer.android.com/games/sdk/frame-pacing#supported_operating_modes
[14] SwapInterval:
https://www.khronos.org/opengl/wiki/Swap_Interval
[15] VBLANK:
https://en.wikipedia.org/wiki/Vertical_blanking_interval
[16] ?ANDROID_get_frame_timestamps :
https://www.khronos.org/registry/EGL/extensions/ANDROID/EGL_ANDROID_get_frame_timestamps.txt
[17] CADisplayLink:
https://developer.apple.com/documentation/quartzcore/cadisplaylink
總結
以上是生活随笔為你收集整理的常说的手机刷新率60Hz、120Hz有什么不同?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 大牛书单 | 迎金秋,与腾讯技术大咖共读
- 下一篇: 大型前端项目的断点调试共享化和复用化实践