Android 高刷新率 90Hz 和 120Hz 渲染,官方解释来了!
The following article is from 谷歌开发者 Author Android
长久以来,手机屏幕刷新率都是 60Hz。应用和游戏开发者也习惯了假定刷新率为 60Hz,也就是每 16.6ms 生成一帧,而且这样开发出来的应用和游戏都会正常进行。但现在的情况已经不同了。
最新的旗舰级设备往往会搭载刷新率更高的屏幕,可以带来更流畅的动画效果、更低的延迟,从而获得更好的整体用户体验。还有一些设备支持可变刷新率,比如 Pixel 4,它支持 60Hz 和 90Hz 两种刷新率。
60Hz 的屏幕每 16.6ms 刷新一次显示内容。这意味着图像显示的时间是 16.6ms 的倍数 (16.6ms、33.3ms、50ms 等)。
支持多种刷新率的屏幕则带来了更多的选择,这些屏幕能以不同的速度进行渲染,并且不会出现抖动。
例如,一个无法维持 60fps 渲染的游戏,在 60Hz 的屏幕上必须一路降到 30fps 才能确保流畅无抖动 (因为显示器只能以 16.6ms 的倍数周期呈现图像,所以 60Hz 的下一档可用帧速是每 33.3ms 显示一帧,即 30fps)。
而在 90Hz 设备上,同样的游戏只需要下降到 45fps (每帧 22.2ms) 即可,这就为用户带来了更流畅的体验。而同时支持 90Hz 和 120Hz 的设备,则可以用每秒 120、90、60 (120/2)、45 (90/2)、40 (120/3)、30 (90/3)、24 (120/5) 等帧率流畅地呈现内容。
渲染频率越高,就越难维持帧率,因为只有更少的时间完成相同的工作量。要在 90Hz 下进行渲染,应用需要在 11.1ms 内生成一帧,与此相比,在 60Hz 时则有 16.6ms 来生成一帧。
应用的 UI 线程处理输入事件,调用应用的回调,并更新视图 (View) 层次结构中记录的绘图命令列表; 应用的 RenderThread 将记录的命令发送到 GPU ; GPU 绘制这一帧; SurfaceFlinger 是负责在屏幕上显示不同应用窗口的系统服务,它会组合出屏幕应该最终显示出的内容,并将画面提交给屏幕的硬件抽象层 (HAL); 屏幕最终呈现该帧的内容。
如上所述,可变刷新率允许我们使用更多样的渲染频率。对于可以控制渲染速度的游戏,以及需要以特定速率呈现内容的视频播放器来说,这一点尤其有用。
例如,要在 60Hz 的显示器上播放 24fps 的视频,我们需要使用 3:2 pulldown 算法,这就会产生抖动。但是,如果设备的屏幕可以原生显示 24fps 的内容 (24/48/72/120Hz),就无需使用 pulldown 算法,自然也就不会出现抖动了。
3:2 pulldown 算法
https://en.wikipedia.org/wiki/Three-two_pull_down
为此,应用可能需要知道当前设备的刷新率。可以通过以下方法来实现:
SDK 通过 DisplayManager.DisplayListener 注册一个显示监听器,并通过 Display.getRefreshRate 查询刷新率。 NDK 使用 AChoreographer_registerRefreshRateCallback 注册回调 (API 级别30)。
DisplayManager.DisplayListener https://developer.android.google.cn/reference/android/hardware/display/DisplayManager#registerDisplayListener(android.hardware.display.DisplayManager.DisplayListener,%20android.os.Handler) Display.getRefreshRate https://developer.android.google.cn/reference/android/view/Display#getRefreshRate() AChoreographer_registerRefreshRateCallback https://developer.android.google.cn/ndk/reference/group/choreographer#achoreographer_registerrefreshratecallback
应用可以通过在其 Window 或 Surface 上设置帧率来影响设备刷新率。这是 Android 11 中引入的一个新功能,允许平台了解应用的渲染需求。应用可以调用以下方法之一:
SDK
Surface.setFrameRate
https://developer.android.google.cn/reference/android/view/Surface#setFrameRate(float,%20int)
SurfaceControl.Transaction.setFrameRate
https://developer.android.google.cn/reference/android/view/SurfaceControl.Transaction.html#setFrameRate(android.view.SurfaceControl,%20float,%20int)
NDK
ANativeWindow_setRrameRate https://developer.android.google.cn/ndk/reference/group/a-native-window#anativewindow_setframerate ASurfaceTransaction_setFrameRate https://developer.android.google.cn/ndk/reference/group/native-activity#asurfacetransaction_setframerate
帧率指南 https://developer.android.google.cn/guide/topics/media/frame-rate
WindowManager.LayoutParams.preferredDisplayModeId https://developer.android.google.cn/reference/android/view/WindowManager.LayoutParams#preferredDisplayModeId Display.getSupportedModes https://developer.android.google.cn/reference/android/view/Display#getSupportedModes()
刷新率并不总是恒定的——如果您想了解实际的刷新率,就需要注册一个回调来知晓刷新率的变动,并相应地更新您应用内部的数据。
如果您没有使用 Android UI 工具包,而使用自定义的渲染器,请考虑根据当前的刷新率来改变您的渲染流水线。
通过使用 OpenGL 上的 eglPresentationTimeANDROID 或 Vulkan 上的 VkPresentationTimesInfoGOOGLE 设置一个呈现时间戳,即可深化流水线。
设置呈现时间戳可以向 SurfaceFlinger 指示何时呈现图像。如果设置为未来的几帧,它就会按照设置的帧数加深流水线。前文例子中的 Android UI 将呈现时间设置成了 frameTimeNanos + 2 * vsyncPeriod。
注: frameTimeNanos 从 Choreographer 获取;vsyncPeriod 从 Display.getRefreshRate() 获取。
eglPresentationTimeANDROID https://www.khronos.org/registry/EGL/extensions/ANDROID/EGL_ANDROID_presentation_time.txt VkPresentationTimesInfoGOOGLE https://www.khronos.org/registry/vulkan/specs/1.1-extensions/man/html/VkPresentTimesInfoGOOGLE.html
实现适当的帧同步 https://developer.android.google.cn/games/sdk/frame-pacing
推荐阅读:
扫一扫 关注我的公众号
如果你想要跟大家分享你的文章,欢迎投稿~
┏(^0^)┛明天见!