Android 10 (Android Q)中的屏幕刷新率(display refresh rate)切换方法和策略 | 您所在的位置:网站首页 › vivo如何查看屏幕分辨率和刷新率 › Android 10 (Android Q)中的屏幕刷新率(display refresh rate)切换方法和策略 |
本文禁止转载,如有需求,请联系作者。 1. 屏幕刷新率和应用的显示帧率首先请区分好屏幕刷新率(Panel Refresh Rate)和应用的帧率(APP Frame Rate)两个概念。 屏幕刷新率是显示器(LCD/OLED等)的硬件性能参数,即它每秒钟能刷新几幅画面,一般用物理单位Hz来表示。我们知道显示器的面板始由物理像素铺满的。比如目前的手机屏幕,主流是1080P的分辨率,那么就是1080*1920个像素点,即每行1080个像素,每列1920个像素。我们一般把显示器画完一行的1080个像素叫做行扫描,把1920行全部画完叫做场扫描,把开始扫描一行的物理信号叫做水平同步信号(HSYNC),把开始扫描一场的物理信号叫做垂直同步信号(VSYNC)。这里的VSYNC就是跟我们要讲的屏幕刷新率直接相关的了。显示器每秒钟能画完几场,就会有多少个VSYNC信号,称为场频,也就是屏幕的刷新率是多少。比如目前大部分手机屏幕的刷新率是60Hz,也就意味着它们每秒钟产生60个VSYNC信号。进而又衍生出另一个概念即场同步周期(vsync period),对于60Hz刷新率的屏幕来说,vsync period就是1000/60=16.6ms。 那什么是应用显示帧率呢?可以认为它是应用画图的速度,一般用fps(frames per second)来表示。比如在微信的聊天界面上下滑动屏幕,微信界面的聊天内容也跟随我们的手指上下移动。假设我们正好要在一秒内往上移动一屏的内容,那么这一屏的内容移动过程在一秒内更新了多少次,是微信应用开发者可以控制的。如果一秒内只更新两三次,你会看到明显的画面丁顿,而如果更新24次以上,你一般就感觉不到卡顿,除非你不是人,是神:)这里应用更新界面的次数,或者应用画图的次数,我们可以理解为应用的显示帧率。当然应用的显示帧率不是越快越好,超过了屏幕刷新率,系统还要做pulldown,反而增加系统的负担。而且应用显示帧率是一个动态的量,因为应用界面并不是总是在变化,当你看新闻的时候,不拖动屏幕,那么显示内容是静态的,当你看一个动画的时候,这个动画的源文件可能是15fps的,也可能是30fps的,也会影响到应用显示帧率。 2. 应用程序(app)与刷新率(refresh rate)上面我们讲了屏幕刷新率和应用显示帧率的概念。现在来看在应用显示设计中,他们的关系。 在应用开发特别是游戏的设计中,有时候对屏幕刷新率是很敏感的。 这里有几个数据,在60Hz刷新率时,场同步周期(vsync period)是1000/60=16.6ms;而在90Hz刷新率时,场同步周期变成1000/90=11.1ms。也就是说,如果应用想要达到跟刷新率相同的显示帧率,在60Hz的时候,他只要在16.6毫秒内画完一幅图就行,而如果想在90Hz刷新率时跟上屏幕刷新率的话,它就只有11.1毫秒的画图时间了。很显然对于动画或界面比较复杂的应用来说,如果在60Hz的时候已经比较吃力的话,在90Hz的时候就必须调整自己的画图方式和步骤,优化自己的效率。从另一个角度看,原来应用程序画动画的时候,只要是能跟60Hz的分频频率合拍(比如15/20/30/...fps),就能保证不会产生抖动(jitter)。但是,如果Android设备运行在90Hz刷新率的话,则合适的数字变成(15/30/45/...)。也就是说,如果应用开发工程师以20fps画图的话,在90Hz刷新率的时候是不合适的,会造成抖动。这就需要应开开发工程师知道Android设备的屏幕刷新率,并根据屏幕刷新率来调整自己的画图速度。或者,实在没办法的话,想办法让设备把屏幕刷新率改成合适的那个。 以前,因为绝大部分Android设备屏幕的显示屏都是60Hz的刷新率,所以游戏开发工程师都会假设他们的应用是运行在60Hz的设备上,从而以在60Hz的刷新率下流畅运行作为开发目标,游戏动画的设计也理论上要求以60的倍频或者分频来设计,比如15/20/30/60/120fps,这样在手机上render的时候正好扣紧vsync的鼓点而不会造成抖动。所以应用设计者不会关心手机的屏幕刷新率是否会改变。但是随着手机屏幕硬件的不断升级,新的机型越来越多的开始支持更高的刷新率,比如90/120Hz。如果这时候仍然按照以前的开发经验来设计应用的话,一来无法体现高刷新率屏幕的优势,二来因为刷新率提升导致vsync period画图时间缩短,可能出现掉帧的情况。 为了跟上技术的更新,应用开发工程师需要从以下4点来优化自己的代码(可参考“多屏幕刷新率(refresh rate)模式下的帧显示流水线”一文): 1. 想办法提升画图的效率,保证在更短的vsync period中画完一幅图,可以引入并行线程; 2. 增加自己的render pipe line; 3. 监测设备屏幕刷新率的变化,根据刷新率的变化调整应用的画图速度; 4. 调用系统接口改变设备的刷新率(如果设备支持的话)。 对于屏幕刷新率只有一个的Android设备,应用的适配相对简单。只要使用Display.getSupportedModes获取设备的刷新率,然后做适配就行。对于支持多个屏幕刷新率的设备,在Android Q上对目前对屏幕刷新率的接口较少。可以使用displayListener或注册回调来获取,或者通过WindowManager.LayoutParams.preferredDisplayModeId来控制。Google在Pixel 4上实现了自动的屏幕刷新率切换,可以根据场景变化在60Hz和90Hz之间自适应的切换。 如果只是想熟悉,可以试试下面的命令来看看不同刷新率下的效果。它并不是把刷新率固定在某个值,只是限定了最高刷新率。如果想同时体验固定帧率和自动帧率,可以参考的手机型号有Motorola Edge和Nubia Red Magic 3。 adb shell settings put system peak_refresh_rate 90.0/60.0 3. Framework层的刷新率接口和策略Google已经在Android Q(10.0)上实现了对屏幕刷新率的操控策略,代码主要在DisplayManagerService和SurfaceFlinger,一个是Java层,一个是native层。 3.1 Java层相关代码DisplayManagerService通过DisplayModeDirector来管理屏幕刷新率。当系统启动ready的时候,DisplayModeDirector会注册。 frameworks/base/services/core/java/com/android/server/display/DisplayManagerService.java 447 /** 448 * Called when the system is ready to go. 449 */ 450 public void systemReady(boolean safeMode, boolean onlyCore) { 451 synchronized (mSyncRoot) { 452 mSafeMode = safeMode; 453 mOnlyCore = onlyCore; 454 mSystemReady = true; 455 // Just in case the top inset changed before the system was ready. At this point, any 456 // relevant configuration should be in place. 457 recordTopInsetLocked(mLogicalDisplays.get(Display.DEFAULT_DISPLAY)); 458 } 459 460 mDisplayModeDirector.setListener(new AllowedDisplayModeObserver()); 461 mDisplayModeDirector.start(); 462 463 mHandler.sendEmptyMessage(MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS); 464 } 而DisplayModeDirector会通过mSettingsObserver和mDisplayObserver两个观察哨来监测setting中用户操作对刷新率的改变和设备显示硬件的变动(比如第二块显示屏幕,或者外接了显示设备,投影等)导致的刷新率改变。 84 public DisplayModeDirector(@NonNull Context context, @NonNull Handler handler) { 85 mContext = context; 86 mHandler = new DisplayModeDirectorHandler(handler.getLooper()); 87 mVotesByDisplay = new SparseArray(); 88 mSupportedModesByDisplay = new SparseArray(); 89 mDefaultModeByDisplay = new SparseArray(); 90 mAppRequestObserver = new AppRequestObserver(); 91 mSettingsObserver = new SettingsObserver(context, handler); 92 mDisplayObserver = new DisplayObserver(context, handler); 93 } SettingObserver的onChange处理函数,和DisplayObserver的onDisplayAdded/onDisplayRemoved/onDisplayChanged处理函数,就是监测setting中用户的操作,和设备显示硬件变化的。 540 public void onChange(boolean selfChange, Uri uri, int userId) { 541 synchronized (mLock) { 543 if (mRefreshRateSetting.equals(uri)) { 545 updateRefreshRateSettingLocked(); 546 } else if (mLowPowerModeSetting.equals(uri)) { 547 updateLowPowerModeSettingLocked(); 548 } 549 } 550 } 690 @Override 691 public void onDisplayAdded(int displayId) { 692 updateDisplayModes(displayId); 693 } 694 695 @Override 696 public void onDisplayRemoved(int displayId) { 697 synchronized (mLock) { 698 mSupportedModesByDisplay.remove(displayId); 699 mDefaultModeByDisplay.remove(displayId); 700 } 701 } 702 703 @Override 704 public void onDisplayChanged(int displayId) { 705 updateDisplayModes(displayId); 706 } 他们都会调用到DisplayModeDirector的notifyAllowedModesChangedLocked()。 371 private void notifyAllowedModesChangedLocked() { 372 if (mListener != null && !mHandler.hasMessages(MSG_ALLOWED_MODES_CHANGED)) { 373 // We need to post this to a handler to avoid calling out while holding the lock 374 // since we know there are things that both listen for changes as well as provide 375 // information. If we did call out while holding the lock, then there's no guaranteed 376 // lock order and we run the real of risk deadlock. 377 Message msg = mHandler.obtainMessage(MSG_ALLOWED_MODES_CHANGED, mListener); 378 msg.sendToTarget(); 379 } 380 } 409 public void handleMessage(Message msg) { 410 switch (msg.what) { 411 case MSG_ALLOWED_MODES_CHANGED: 412 Listener listener = (Listener) msg.obj; 413 listener.onAllowedDisplayModesChanged(); 414 break; 415 } 416 } 并最终调用DisplayManagerService的onAllowedDisplayModesChangedInternal()。其中display.setAllowedDisplayModesLocked则会通过SurfaceControl.setAllowedDisplayConfigs传递给native层SurfaceFlinger的屏幕刷新率Schedule策略模块。 1422 private void onAllowedDisplayModesChangedInternal() { 1423 boolean changed = false; 1424 synchronized (mSyncRoot) { 1425 final int count = mLogicalDisplays.size(); 1426 for (int i = 0; i < count; i++) { 1427 LogicalDisplay display = mLogicalDisplays.valueAt(i); 1428 int displayId = mLogicalDisplays.keyAt(i); 1429 int[] allowedModes = mDisplayModeDirector.getAllowedModes(displayId); 1430 // Note that order is important here since not all display devices are capable of 1431 // automatically switching, so we do actually want to check for equality and not 1432 // just equivalent contents (regardless of order). 1433 if (!Arrays.equals(allowedModes, display.getAllowedDisplayModesLocked())) { 1434 display.setAllowedDisplayModesLocked(allowedModes); 1435 changed = true; 1436 } 1437 } 1438 if (changed) { 1439 scheduleTraversalLocked(false); 1440 } 1441 } 1442 } 3.2 Native层相关代码上面讲到DisplayManagerService调用SurfaceControl.setAllowedDisplayConfigs接口来影响屏幕刷新率,通过JNI接口和libgui,直接call到了SurfaceFlinger服务. frameworks/base/core/jni/android_view_SurfaceControl.cpp:: nativeSetAllowedDisplayConfigs(); frameworks/native/libs/gui/SurfaceComposerClient.cpp:: getComposerService()->setAllowedDisplayConfigs(displayToken, allowedConfigs); 上面的getComposerService()就是得到SurfaceFlinger的服务句柄,进而调用它的接口setAllowedDisplayConfigs来对屏幕刷新率做更新。请注意这个接口里传下来的allowedConfigs是一个数组,不一定是某一个刷新率的索引。 SurfaceFlinger通过mRefreshRateConfigs和mAllowedDisplayConfigs来维护Android设备支持的屏幕刷新率和系统目前允许使用的屏幕刷新率。比如有一款手机,它的显示屏可以支持60Hz、90Hz、120Hz三个刷新率,则mRefreshRateConfigs则记录了这3个刷新率。而应用或者系统可以通过setAllowedDisplayConfigs让SurfaceFlinger只能在某几个刷新率之间切换,就是设置的mAllowedDisplayConfigs,比如只让这个手机在60Hz和90Hz之间切换,则mAllowedDisplayConfigs就只记录了60Hz和90Hz。 SurfaceFlinger在初始化时,会对mRefreshRateConfigs做初始化。其中getHwComposer().getConfigs是从HAL层获取设备支持的所有屏幕刷新率。 754 void SurfaceFlinger::init() { ...... 890 int active_config = getHwComposer().getActiveConfigIndex(*display->getId()); 891 mRefreshRateConfigs.setActiveConfig(active_config); 892 mRefreshRateConfigs.populate(getHwComposer().getConfigs(*display->getId())); 893 mRefreshRateStats.setConfigMode(active_config); ...... } 如前面讲JAVA层代码时提到过,系统开机过程中DisplayObserver的onDisplayAdded()会被调到,进而调用setAllowedDisplayConfigs来初始化mAllowedDisplayConfigs。一般来说,开机时初始化的mRefreshRateConfigs和mAllowedDisplayConfigs是一致的,除非设备开发商做了定制,比如让设备开机默认固定在某个刷新率。比如motorola edge开机默认是auto模式,它开机后mRefreshRateConfigs和mAllowedDisplayConfigs是一致的。而motorola edge plus开机后默认是90Hz模式,它开机后mRefreshRateConfigs和mAllowedDisplayConfigs就不一致。 为了保证从上层传过来的allowedConfigs在本机支持的刷新率内,在setAllowedDisplayConfigs时还用经过mRefreshRateConfigs的筛选。在mRefreshRateConfigs.getAllowedConfigs中,会根据设备的支持情况更新displayConfigs。 7661 void SurfaceFlinger::setAllowedDisplayConfigsInternal(const sp& display, 7662 const std::vector& allowedConfigs) { 7672 7676 // Set Phase Offsets type for the Default Refresh Rate config. 7677 RefreshRateType type = mRefreshRateConfigs.getDefaultRefreshRateType(); 7678 mPhaseOffsets->setDefaultRefreshRateType(type); 7679 7685 // Update the allowed Display Configs. 7686 mRefreshRateConfigs.getAllowedConfigs(getHwComposer().getConfigs(*display->getId()), 7687 &displayConfigs); 7688 7689 mAllowedDisplayConfigs = DisplayConfigs(displayConfigs.begin(), displayConfigs.end()); 7693 7711 setPreferredDisplayConfig(); 7712 } 改变mAllowedDisplayConfigs除了使用SurfaceFlinger服务的setAllowedDisplayConfigs接口外,还可以使用SurfaceFlinger的transact code 1035。这个对调试非常有用。你可以使用类似下面的命令行来手动切换刷新率: adb shell service call SurfaceFlinger 1035 i32 X(X是所要切换的屏幕刷新率的索引) 在Android Q不断迭代的后期,Google也加入了更多的直观的调试方法来让系统开发者监控设备当前的屏幕刷新率变化,比如在屏幕的左上角增加颜色方块或者数字来直观的显示当前的刷新率,同时在开发者选项中增加开关来控制这个调试功能(Android R上是肯定有的,目前Q上开发者选项里还没看到,但可以使用命令行来开关)。 回到重点,那么,SurfaceFlinger中真正的调用底层切换屏幕刷新率的地方在哪里呢?它在刷新下一帧的准备阶段。我们继续看setAllowedDisplayConfigs接口,它最后调用了setPreferredDisplayConfig()。如果设备上enable了Google的auto切换策略,则从策略中获取最合适的刷新率,如果没有enable,则找到设备支持的最大刷新率。当然,都是在allowed的前提下。然后通过接口setDesiredActiveConfig往下调。 7638 void SurfaceFlinger::setPreferredDisplayConfig() { 7639 const auto& type = mScheduler->getPreferredRefreshRateType(); 7640 const auto& config = mRefreshRateConfigs.getRefreshRate(type); 7641 if (config && isDisplayConfigAllowed(config->configId)) { 7642 ALOGV("switching to Scheduler preferred config %d", config->configId); 7643 setDesiredActiveConfig({type, config->configId, Scheduler::ConfigEvent::Changed}); 7644 } else { 7645 // Set the highest allowed config by iterating backwards on available refresh rates 7646 const auto& refreshRates = mRefreshRateConfigs.getRefreshRates(); 7647 for (auto iter = refreshRates.crbegin(); iter != refreshRates.crend(); ++iter) { 7648 if (iter->second && isDisplayConfigAllowed(iter->second->configId)) { 7651 mRefreshRateConfigs.setActiveConfig(iter->second->configId); 7653 setDesiredActiveConfig({iter->first, iter->second->configId, 7654 Scheduler::ConfigEvent::Changed}); 7655 break; 7656 } 7657 } 7658 } 7659 } 我们继续看setDesiredActiveConfig接口。它只是把选择好的ActiveConfigInfo赋值给mDesiredActiveConfig并记一个标记mDesiredActiveConfigChanged=true。 1194 void SurfaceFlinger::setDesiredActiveConfig(const ActiveConfigInfo& info) { ...... 1202 mDesiredActiveConfig = info; 1203 mDesiredActiveConfig.event = mDesiredActiveConfig.event | prevConfig; ...... 1219 mDesiredActiveConfigChanged = true; ...... 1225 } 上面标记的结果的真正使用是在SurfaceFlingerm每次收到MessageQueue::INVALIDATE信号并调用performSetActiveConfig()的时候。这里首先判断mCheckPendingFence和previousFrameMissed,如果mCheckPendingFence被标记并且前一个frame没有被drop,则表示刚刚前一个frame刷新过程中改变了屏幕刷新率并成功了,这时需要调用setActiveConfigInternal去更新SF的跟显示性能相关的vsync offset等参数。如果mCheckPendingFence是false则继续判断是否需要改变屏幕刷新率,如果需要的话,则用getHwComposer().setActiveConfig发命令给HAL并标记mCheckPendingFence和触发一次INVALIDATE。 1281 bool SurfaceFlinger::performSetActiveConfig() { 1282 ATRACE_CALL(); 1283 if (mCheckPendingFence) { 1284 if (previousFrameMissed()) { 1285 // fence has not signaled yet. wait for the next invalidate 1286 mEventQueue->invalidate(); 1287 return true; 1288 } 1290 // We received the present fence from the HWC, so we assume it successfully updated 1291 // the config, hence we update SF. 1292 mCheckPendingFence = false; 1293 setActiveConfigInternal(); 1294 } 1295 1296 // Store the local variable to release the lock. 1303 desiredActiveConfig = mDesiredActiveConfig; 1322 mUpcomingActiveConfig = desiredActiveConfig; 1327 getHwComposer().setActiveConfig(*displayId, mUpcomingActiveConfig.configId); 1328 1340 // we need to submit an empty frame to HWC to start the process 1341 mCheckPendingFence = true; 1342 mEventQueue->invalidate(); 1343 return false; 1344 } 不要在SurfaceFlinger中随便调用HidlHAL的setActiveConfig()去设置硬件刷新率。因为这样的话虽然硬件的刷新率变了,但是SurfaceFlinger和RenderThread并不知道,因此相关的pipeline性能相关的参数没有同步更新,会造成显示性能的未知问题。比如mVsyncModulator的setPhaseOffsets就是很重要的参数,它决定了SF和APP开始画图的针对vsync的偏移时间。只有调用SurfaceFlinger标准接口来设置刷新率,才能通过setActiveConfigInternal更新所有的刷新参数并通知关心的上层模块。 1243 void SurfaceFlinger::setActiveConfigInternal() { 1250 1251 std::lock_guard lock(mActiveConfigLock); 1252 mRefreshRateStats.setConfigMode(mUpcomingActiveConfig.configId); 1253 1254 display->setActiveConfig(mUpcomingActiveConfig.configId); 1255 1256 mPhaseOffsets->setRefreshRateType(mUpcomingActiveConfig.type); 1257 const auto [early, gl, late] = mPhaseOffsets->getCurrentOffsets(); 1258 mVsyncModulator.setPhaseOffsets(early, gl, late, 1259 mPhaseOffsets->getOffsetThresholdForNextVsync()); 1260 ATRACE_INT("ActiveConfigMode", mUpcomingActiveConfig.configId); 1261 1262 if (mUpcomingActiveConfig.event != Scheduler::ConfigEvent::None) { 1263 mScheduler->onConfigChanged(mAppConnectionHandle, display->getId()->value, 1264 mUpcomingActiveConfig.configId); 1265 } 1266 } 3.3 SurfaceFlinger中的屏幕刷新率切换策略在SurfaceFlinger的RefreshRateConfigs类中,有Google目前的屏幕刷新率相关的一些定义。比如它定义了默认的刷新率是60Hz,并定义了RefreshRateType,其中DEFAULT就对应60Hz。 35 class RefreshRateConfigs { 37 static const int DEFAULT_FPS = 60; 39 public: 44 enum class RefreshRateType {POWER_SAVING, LOW0, LOW1, LOW2, DEFAULT, PERFORMANCE, HIGH1, HIGH2}; 99 } 在SurfaceFlinger服务启动成功的时候,会调用setRefreshRateTo接口把屏幕刷新率设置为设备支持的最高刷新率。然后如果setting里有默认值的话,会通过setAllowedDisplayConfigs再改变一次。 643 void SurfaceFlinger::bootFinished() 644 { ...... 698 // set the refresh rate according to the policy 699 int maxSupportedType = (int)RefreshRateType::HIGH2; 700 int minSupportedType = (int)RefreshRateType::LOW0; 701 702 for (int type = maxSupportedType; type >= minSupportedType; type--) { 703 RefreshRateType refreshRateType = static_cast(type); 704 const auto& refreshRate = mRefreshRateConfigs.getRefreshRate(refreshRateType); 705 if (refreshRate && isDisplayConfigAllowed(refreshRate->configId)) { 706 setRefreshRateTo(refreshRateType, Scheduler::ConfigEvent::None); 707 return; 708 } 709 } ...... xxx } 等这一切都完成后,除非用户在setting里做手动操作,屏幕刷新率就不会变化了。如果想要让屏幕刷新率根据场景进行自适应的变化,就需要加入自己的策略,或者打开Google的Schedule策略。自己加策略大家可以百花齐放,我们这里说说Googel的Schedule策略。 我们看SurfaceFlinger的init()函数,一上来就拿到一个mScheduler句柄。这个mScheduler的一项重要功能,就是管理屏幕刷新率的策略。 754 void SurfaceFlinger::init() { 755 ALOGI( "SurfaceFlinger's main thread ready to run. Initializing graphics H/W..."); 760 Mutex::Autolock _l(mStateLock); 762 mScheduler = getFactory().createScheduler([this](bool enabled) { setPrimaryVsyncEnabled(enabled); }, xxx } 它并没有创建一个线程去监控场景(SurfaceFlinger里的线程已经够多了),而是使用了两个timer(Q的后期又添加了一个,我们这里不讲)。一个是mIdleTimer,一个mTouchTimer。Google这个刷新率策略的初衷就是,让用户在屏幕上手动操作的时候,纵享丝滑,体验到如丝般顺滑。其他时候,是怎样就怎样吧。 来看Scheduler的构造函数,两个timer在这里开始了他们的人生,每个人都在SF的操控下处理两件事情,resetTimerCallback和expiredTimerCallback。 60 Scheduler::Scheduler(impl::EventControlThread::SetVSyncEnabledFunction function, 61 const scheduler::RefreshRateConfigs& refreshRateConfig) 62 : mHasSyncFramework(running_without_sync_framework(true)), 63 mDispSyncPresentTimeOffset(present_time_offset_from_vsync_ns(0)), 64 mPrimaryHWVsyncEnabled(false), 65 mHWVsyncAvailable(false), 66 mRefreshRateConfigs(refreshRateConfig) { 74 75 mSetIdleTimerMs = set_idle_timer_ms(0); 78 mSetTouchTimerMs = set_touch_timer_ms(0); 126 127 if (mSetIdleTimerMs > 0) { 137 mIdleTimer = std::make_unique(std::chrono::milliseconds( 138 mSetIdleTimerMs), 139 [this] { resetTimerCallback(); }, 140 [this] { expiredTimerCallback(); }); 142 mIdleTimer->start(); 143 } 144 145 if (mSetTouchTimerMs > 0) { 146 // Touch events are coming to SF every 100ms, so the timer needs to be higher than that 147 mTouchTimer = 148 std::make_unique(std::chrono::milliseconds(mSetTouchTimerMs), 149 [this] { resetTouchTimerCallback(); }, 150 [this] { expiredTouchTimerCallback(); }); 151 mTouchTimer->start(); 152 } 164 } Google把mIdleTimer和mTouchTimer的timeout时间分别设定在500毫秒和2000毫秒。这两个timer的控制逻辑具体是这样: 当用户手动在触屏上操作时,会触发notifyPowerHint,进而reset mTouchTimer,在resetTouchTimerCallback中,把刷新率类型改成PERFORMANCE(90Hz),等到mTouchTimer timeout的时候,维持当前的刷新率,等于无操作。当屏幕显示内容有更新时,会resetIdleTimer(),而resetTimerCallback没什么操作,只有等到IdleTimer timeout时,在expiredTimerCallback()的时候,把刷新率改为DEFAULT(60Hz)。部分代码如下,其中handleTimerStateChanged和calculateRefreshRateType是策略的灵魂: 当系统touch事件reset touch timer时,把mCurrentTouchState改为TouchState::ACTIVE,而在idle timer expired的时候,把mCurrentIdleTimerState改为IdleTimerState::EXPIRED。 frameworks/native/libs/gui/ISurfaceComposer.cpp 1060 status_t BnSurfaceComposer::onTransact( 1061 uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) 1062 { 1063 switch(code) { 1685 case NOTIFY_POWER_HINT: { //从inputmanager来的事件 1686 CHECK_INTERFACE(ISurfaceComposer, data, reply); 1687 int32_t hintId; 1688 status_t error = data.readInt32(&hintId); 1689 if (error != NO_ERROR) { 1690 ALOGE("notifyPowerHint: failed to read hintId: %d", error); 1691 return error; 1692 } 1693 return notifyPowerHint(hintId); 1694 } 1716 } 1717 } frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp 1697 status_t SurfaceFlinger::notifyPowerHint(int32_t hintId) { 1698 PowerHint powerHint = static_cast(hintId); 1699 1700 if (powerHint == PowerHint::INTERACTION) { 1701 mScheduler->notifyTouchEvent(); //reset touch timer 1702 } 1703 1704 return NO_ERROR; 1705 } 1729 void SurfaceFlinger::signalTransaction() { 1730 mScheduler->resetIdleTimer(); 1731 mEventQueue->invalidate(); 1732 } 1733 1734 void SurfaceFlinger::signalLayerUpdate() { 1735 mScheduler->resetIdleTimer(); 1736 mEventQueue->invalidate(); 1737 } 536 void Scheduler::resetTouchTimerCallback() { 537 handleTimerStateChanged(&mCurrentTouchState, TouchState::ACTIVE, true); 538 ATRACE_INT("TouchState", 1); 539 } 528 void Scheduler::expiredTimerCallback() { 532 handleTimerStateChanged(&mCurrentIdleTimerState, IdleTimerState::EXPIRED, false); 533 ATRACE_INT("ExpiredIdleTimer", 1); 534 } 576 template 577 void Scheduler::handleTimerStateChanged(T* currentState, T newState, bool eventOnContentDetection) { 578 ConfigEvent event = ConfigEvent::None; 579 RefreshRateType newRefreshRateType; 580 { 581 std::lock_guard lock(mFeatureStateLock); 582 if (*currentState == newState) { 583 return; 584 } 585 *currentState = newState; //改变current state 586 newRefreshRateType = calculateRefreshRateType(); 587 if (mRefreshRateType == newRefreshRateType) { 588 return; 589 } 590 mRefreshRateType = newRefreshRateType; 591 if (eventOnContentDetection && 592 mCurrentContentFeatureState == ContentFeatureState::CONTENT_DETECTION_ON) { 593 event = ConfigEvent::Changed; 594 } 595 } 596 changeRefreshRate(newRefreshRateType, event); 597 } 下面是策略所在,在calculateRefreshRateType()中,如果发现当前mCurrentTouchState是TouchState::ACTIVE,则返回Performance,如果mCurrentIdleTimerState是IdleTimerState::EXPIRED,会返回DEFAULT。 604 Scheduler::RefreshRateType Scheduler::calculateRefreshRateType() { ...... 649 // As long as touch is active we want to be in performance mode 650 if (mCurrentTouchState == TouchState::ACTIVE) { 651 REFRESH_RATE_RESULT(RESULT_TOUCH_ACTIVE); 652 return RefreshRateType::PERFORMANCE; 653 } ...... 655 // If timer has expired as it means there is no new content on the screen 656 if (mCurrentIdleTimerState == IdleTimerState::EXPIRED) { 657 REFRESH_RATE_RESULT(RESULT_IDLE_EXPIRED); 658 return RefreshRateType::DEFAULT; 659 } ...... 707 return currRefreshRateType; 708 } 策略函数给出的刷新率,会在handleTimerStateChanged中调用changeRefreshRate callback到SurfaceFlinger中,通过setRefreshRateTo接口往下传递,后面的调用关系,就基本跟setAllowedDisplayConfigs一致了。 综上所诉,Google的刷新率策略如我所说,就是让你动手的时候,充分体验到高刷新率屏幕的硬件优势。至于其他情况,比如,有个应用界面,上面有个按钮,你点击这个按钮后,会播放一段动画。在你点击按钮的时候,Google的刷新率策略触发屏幕刷新率被改成90Hz,那么你看按钮按下抬起这个效果是很顺畅的。但是如果你的动画开始显示第一帧的时间跟按钮抬起结束的时间之间的间隔大于Idletimer的500毫秒生命周期的话,对不起,刷新率又被策略改回60Hz了。虽然你很想让动画也享受到90Hz的极致体验,但是奈何掌权者不给啊。所在Google的自适应策略下,如果想在用户操作后保持90Hz,你需要不停的更新显示内容,且每次更新之间不能大于500毫秒。这就是为什么很多支持高刷新的产品会设计固定刷新率的模式让用户做选择,因为只有在固定高刷新率的情况下,才能满足用户永远丝滑感受。当然,固定在90Hz也会带来不确定的问题,比如功耗的提高,比如对那些针对60Hz做过精心调试的游戏是否有负面效果。 4. HAL层刷新率相关代码上面讲了Android系统中APP和frameworks中跟显示刷新率和帧率相关的代码和策略。因为代码比较common,大家都可以做参考。现在讲到HAL层,因为不同平台的实现方法会有不同,我们就只依据高通平台的HAL粗略提一下,不做展开。 前面有提到SurfaceFlinger是调用setActiveConfig()到HidlHAL去设置硬件刷新率的。在高通的HAL层,是一级级调到DisplayBase的SetActiveConfig接口,然后调用DRM HAL接口SetDisplayAttributes和UpdateMixerAttributes(), 去更新全局变量current_mode_index_和标记update_mode_ = true。current_mode_index_是记录系统当前的刷新率索引,update_mode_为true表示需要更新刷新率。 656 DisplayError DisplayBase::SetActiveConfig(uint32_t index) { ...... 673 error = hw_intf_->SetDisplayAttributes(index); ...... 678 return ReconfigureDisplay(); 679 } 在SetDisplayAttributes还要确认一下所选的刷新率是否跟当前显示硬件的属性匹配: 847 DisplayError HWDeviceDRM::SetDisplayAttributes(uint32_t index) { ...... 875 for (uint32_t mode_index = 0; mode_index < connector_info_.modes.size(); mode_index++) { 876 if ((to_set.vdisplay == connector_info_.modes[mode_index].mode.vdisplay) && 877 (to_set.hdisplay == connector_info_.modes[mode_index].mode.hdisplay) && 878 (to_set.vrefresh == connector_info_.modes[mode_index].mode.vrefresh) && 879 (current_bit_clk == connector_info_.modes[mode_index].bit_clk_rate) && 880 (mode_flag & connector_info_.modes[mode_index].mode.flags)) { 881 index = mode_index; 882 break; 883 } 884 } 885 886 current_mode_index_ = index; 887 PopulateHWPanelInfo(); 888 UpdateMixerAttributes(); ...... 902 return kErrorNone; 903 } 2023 void HWDeviceDRM::UpdateMixerAttributes() { 2024 uint32_t index = current_mode_index_; 2025 2026 mixer_attributes_.width = display_attributes_[index].x_pixels; 2027 mixer_attributes_.height = display_attributes_[index].y_pixels; 2028 mixer_attributes_.split_left = display_attributes_[index].is_device_split 2029 ? hw_panel_info_.split_info.left_split 2030 : mixer_attributes_.width; 2031 mixer_attributes_.split_type = kNoSplit; ...... 2043 update_mode_ = true; 2044 } 最后向kernel层发命令是在每帧刷新的Validate函数调用的SetupAtomic()中。它发送DRM驱动的property set code 命令CRTC_SET_MODE 给kernel,来改变驱动的刷新率。HAL层还有自己的刷新率更新策略来应对显示设备的变化,具体可以跟踪first_cycle_ 、vrefresh_ 、panel_mode_changed_这几个变量的变化。 1393 DisplayError HWDeviceDRM::Validate(HWLayers *hw_layers) { ...... 1398 SetupAtomic(hw_layers, true /* validate */); 1399 1400 int ret = drm_atomic_intf_->Validate(); ...... 1409 return err; 1410 } 1076 void HWDeviceDRM::SetupAtomic(HWLayers *hw_layers, bool validate) { ...... 1337 // Set CRTC mode, only if display config changes 1338 if (first_cycle_ || vrefresh_ || update_mode_ || panel_mode_changed_) { 1339 drm_atomic_intf_->Perform(DRMOps::CRTC_SET_MODE, token_.crtc_id, ¤t_mode); 1340 } ...... 1352 } 5. kernel中刷新率的设定高通平台的DRM驱动比较复杂,且一般不需要大的修改。我们这里只提一下不同刷新率在device tree里的设定。在panel driver bringup阶段,从panel的spec中找出所支持的刷新率,并从IC vendor支持那里拿到相应的timing参数以后,就可以在device tree里生成该panel的device tree。关键的几个值是“qcom,mdss-dsi-panel-framerate”、“qcom,mdss-dsi-timing-switch-command”、“qcom,mdss-dsi-timing-switch-command-state”。 qcom,mdss-dsi-display-timings { timing@0{ // 60 FPS qcom,mdss-dsi-cmd-mode; qcom,mdss-dsi-panel-framerate = ; ....... qcom,mdss-dsi-timing-switch-command = [.....]; qcom,mdss-dsi-on-command = [......]; qcom,mdss-dsi-off-command = [......]; qcom,mdss-dsi-timing-switch-command-state = "dsi_lp_mode"; ...... }; timing@1{ // 90 FPS qcom,mdss-dsi-cmd-mode; qcom,mdss-dsi-panel-framerate = ; ....... qcom,mdss-dsi-timing-switch-command = [......]; qcom,mdss-dsi-on-command = [......]; qcom,mdss-dsi-off-command = [......]; qcom,mdss-dsi-timing-switch-command-state = "dsi_lp_mode"; ...... }; }; 6. 综述 以上,在Android系统中跟屏幕刷新率相关的关键代码就理完了。虽然设备硬件的更新换代,相信大家会越来越多关注这方面对Android软件开发的影响。这里抛砖引玉,希望在Android的持续迭代过程中,与大家共同进步。欢迎不吝指正。 本文禁止转载,如有需求,请联系作者。 |
CopyRight 2018-2019 实验室设备网 版权所有 |