背景
1. 帧率(FrameRate)
2. 刷新率(RefreshRate)
刷新率与帧率是两个独立的概念,帧率表示驱动显示器的设备每秒可产生新图像的数量。
-
游戏引擎与驱动是生产者,工作效率用帧率来评价;
-
显示设备是消费者,工作效率用刷新率来评价。
3. 画面撕裂(ScreenTearing)
4. 垂直同步(Vertical Synchronization)
(1)NVIDIA G-Sync
(2)AMD FreeSync
移动平台
1. Android
游戏逻辑和渲染循环与安卓系统和显示屏硬件之间有一个同步的关系,这个同步过程我们称为帧节奏(Frame Pacing),也即引擎与CPU、GPU配合产生图像的帧率 与显示屏刷新率之间的同步关系。
-
将历史帧数据缓存住;
-
自动检测有延迟的帧数据提交;
-
当提交有延迟时,重复渲染历史帧数据。
(1)短帧卡顿
(2)解决短帧卡顿
(3)长帧卡顿/延迟
-
A延续了三帧;
-
B只展示了一帧。
(4)解决长帧卡顿
2. Frame Pacing Library
-
60Hz:60FPS/30FPS/20FPS
-
60 + 90Hz:90FPS/60FPS/45FPS/30FPS
-
60 + 90 + 120Hz:120FPS/90FPS/60FPS/45FPS/40FPS/30FPS
3. iOS
(1)设置固定帧率
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 10
displayLinkParam.preferredFramesPerSecond = MaxRefreshRate / FIOSPlatformRHIFramePacer::FrameInterval;
// pre iOS 10
displayLinkParam.frameInterval = FIOSPlatformRHIFramePacer::FrameInterval;
即可通过历史帧的数据动态调整FrameInterval或期望FPS,以达到更流畅的视觉体验。
5. Unity
Unity2019.2之后在安卓平台整合了Swappy作为FramePacer。 Unity2018版本仅设置了glSwapInterval,即通过不是很精确的timestamp模式控制FramePacing: // Runtime/GfxDevice/egl/WindowContextEGL.cpp
EGLint 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有什么不同?
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论