android R Variable Refresh Rate 可变帧率 VRR
android R 可变帧率VRR
- 前言
- API简述
- Graphics相关代码分析
- 1.用户设置刷新率流程
- 2.SurfaceFlinger设置刷新率
- 使用API以及验证
前言
本文代码基于Android R r1版本。如果不是这个版本的代码,可能会有部分代码上的差异。
API简述
Surface.setFrameRate(float frameRate, int compatibility)
//frameworks/base/core/java/android/view/Surface.javaSurfaceControl.Transaction.setFrameRate(float frameRate, int compatibility)
//frameworks/base/core/java/android/view/SurfaceControl.javaANativeWindow_setFrameRate(float frameRate, int8_t compatibility)
//frameworks/native/libs/nativewindow/ANativeWindow.cppASurfaceTransaction_setFrameRate(float frameRate, int8_t compatibility)
//frameworks/base/native/android/surface_control.cpp
分别有两个参数:
参数frameRate表示要设置的帧率,设置0表示接受系统刷新率,设置其余的整数都可以,系统会默认适配一个正确的刷新率。
参数compatibility有两个值可选,分别是
FRAME_RATE_COMPATIBILITY_DEFAULT和
FRAME_RATE_COMPATIBILITY_FIXED_SOURCE。
其中FRAME_RATE_COMPATIBILITY_DEFAULT被设置之后,不能保证设置的帧率一定生效,app要保证在未生效的刷新率下正常运行,同时这个参数只能被用在非视频场景下。FRAME_RATE_COMPATIBILITY_FIXED_SOURCE这个值只能在视频场景下被使用。
Graphics相关代码分析
1.用户设置刷新率流程
无论是SDK中的接口还是NDK中的接口最终都会调用到surfaceflinger中。
status_t SurfaceFlinger::setFrameRate(const sp<IGraphicBufferProducer>& surface, float frameRate,int8_t compatibility)
进入setFrameRate之后,首先是判断参数的有效性。这个判断就是验证frameRate的值是否大于0,是否为浮点数。然后再判断compatibility的值是否为两个枚举值。
bool ValidateFrameRate(float frameRate, int8_t compatibility, const char* inFunctionName) {const char* functionName = inFunctionName != nullptr ? inFunctionName : "call";int floatClassification = std::fpclassify(frameRate);if (frameRate < 0 || floatClassification == FP_INFINITE || floatClassification == FP_NAN) {ALOGE("%s failed - invalid frame rate %f", functionName, frameRate);return false;}if (compatibility != ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT &&compatibility != ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE) {ALOGE("%s failed - invalid compatibility value %d", functionName, compatibility);return false;}return true;
}
接下来就是一个lamda表达式。
static_cast<void>(schedule([=] {Mutex::Autolock lock(mStateLock);if (authenticateSurfaceTextureLocked(surface)) {sp<Layer> layer = (static_cast<MonitoredProducer*>(surface.get()))->getLayer();if (layer->setFrameRate(Layer::FrameRate(frameRate,Layer::FrameRate::convertCompatibility(compatibility)))) {setTransactionFlags(eTraversalNeeded);}} else {ALOGE("Attempt to set frame rate on an unrecognized IGraphicBufferProducer");return BAD_VALUE;}return NO_ERROR;}));
调用的surfaceflinger.cpp中的模板方法。
template <typename F, typename T>
inline std::future<T> SurfaceFlinger::schedule(F&& f) {auto [task, future] = makeTask(std::move(f));mEventQueue->postMessage(std::move(task));return std::move(future);
}
makeTask定义在MessageQueue.h中。
template <typename F>
class Task : public MessageHandler {template <typename G>friend auto makeTask(G&&);explicit Task(F&& f) : mTask(std::move(f)) {}void handleMessage(const Message&) override { mTask(); }using T = std::invoke_result_t<F>;std::packaged_task<T()> mTask;
};template <typename F>
inline auto makeTask(F&& f) {sp<Task<F>> task = new Task<F>(std::move(f));return std::make_pair(task, task->mTask.get_future());
}
setFrameRate中的lambda表达式被封装成MessageHander之后postMessage到MessageQueue中去。再Looper中pollOnce的时候被取出来执行。这个时候才会真正调用setFrameRate中的lambda表达式。再看这个表达式是怎么写的。authenticateSurfaceTextureLocked就是判断producer是否存在。然后在调用layer的setFrameRate。
bool Layer::setFrameRate(FrameRate frameRate) {if (!mFlinger->useFrameRateApi) {return false;}if (mCurrentState.frameRate == frameRate) {return false;}// Activate the layer in Scheduler's LayerHistorymFlinger->mScheduler->recordLayerHistory(this, systemTime(),LayerHistory::LayerUpdateType::SetFrameRate);mCurrentState.sequence++;mCurrentState.frameRate = frameRate;mCurrentState.modified = true;updateTreeHasFrameRateVote();setTransactionFlags(eTransactionNeeded);return true;
}
首先是调用scheduler的layerhistory,实现在LayerHistoryV2中。
void LayerHistoryV2::record(Layer* layer, nsecs_t presentTime, nsecs_t now,LayerUpdateType updateType) {std::lock_guard lock(mLock);const auto it = std::find_if(mLayerInfos.begin(), mLayerInfos.end(),[layer](const auto& pair) { return pair.first == layer; });LOG_FATAL_IF(it == mLayerInfos.end(), "%s: unknown layer %p", __FUNCTION__, layer);const auto& info = it->second;info->setLastPresentTime(presentTime, now, updateType, mConfigChangePending);// Activate layer if inactive.if (const auto end = activeLayers().end(); it >= end) {std::iter_swap(it, end);mActiveLayersEnd++;}
}
根据layer从mLayerInfos中取出对应的LayerPair,LayerPair就是一个Layer和LayerInfo的pair。然后将参数中的各种属性设置到LayerInfoV2的setLastPresentTime中去。
void LayerInfoV2::setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now,LayerUpdateType updateType, bool pendingConfigChange) {lastPresentTime = std::max(lastPresentTime, static_cast<nsecs_t>(0));mLastUpdatedTime = std::max(lastPresentTime, now);switch (updateType) {case LayerUpdateType::AnimationTX:mLastAnimationTime = std::max(lastPresentTime, now);break;case LayerUpdateType::SetFrameRate:case LayerUpdateType::Buffer:FrameTimeData frameTime = {.presetTime = lastPresentTime,.queueTime = mLastUpdatedTime,.pendingConfigChange = pendingConfigChange};mFrameTimes.push_back(frameTime);if (mFrameTimes.size() > HISTORY_SIZE) {mFrameTimes.pop_front();}break;}
}
这个函数的作用就是记录图层的请求时间,如果now比最后一次请求的时间更大就把now当做最后一次请求的时间。
再往下也没有什么特殊的代码了,就是遍历图层,设置flag。到这里,设置refresh rate的代码就结束了。但是到这里并不表示设置refresh rate成功。refresh rate也不会是你设置多少就变成多少。
2.SurfaceFlinger设置刷新率
在sf中的onMessageInvalidate函数中,还会根据layer history决定最终设置怎么样的refresh rate。
这是在scheduler中的调用。
void Scheduler::chooseRefreshRateForContent() {if (!mLayerHistory) return;ATRACE_CALL();scheduler::LayerHistory::Summary summary = mLayerHistory->summarize(systemTime());HwcConfigIndexType newConfigId;{std::lock_guard<std::mutex> lock(mFeatureStateLock);if (mFeatures.contentRequirements == summary) {return;}mFeatures.contentRequirements = summary;mFeatures.contentDetectionV1 =!summary.empty() ? ContentDetectionState::On : ContentDetectionState::Off;scheduler::RefreshRateConfigs::GlobalSignals consideredSignals;newConfigId = calculateRefreshRateConfigIndexType(&consideredSignals);if (mFeatures.configId == newConfigId) {// We don't need to change the config, but we might need to send an event// about a config change, since it was suppressed due to a previous idleConsideredif (!consideredSignals.idle) {dispatchCachedReportedConfig();}return;}mFeatures.configId = newConfigId;auto& newRefreshRate = mRefreshRateConfigs.getRefreshRateFromConfigId(newConfigId);mSchedulerCallback.changeRefreshRate(newRefreshRate,consideredSignals.idle ? ConfigEvent::None: ConfigEvent::Changed);}
}
上面setFrameRate的时候调用的setLastPresentTime在这边就用上了。这里先调用了mLayerHistory的summarize。看下summarize的实现。
LayerHistoryV2::Summary LayerHistoryV2::summarize(nsecs_t now) {LayerHistory::Summary summary;std::lock_guard lock(mLock);partitionLayers(now);for (const auto& [layer, info] : activeLayers()) {const auto strong = layer.promote();if (!strong) {continue;}const auto frameRateSelectionPriority = strong->getFrameRateSelectionPriority();const auto layerFocused = Layer::isLayerFocusedBasedOnPriority(frameRateSelectionPriority);ALOGV("%s has priority: %d %s focused", strong->getName().c_str(),frameRateSelectionPriority, layerFocused ? "" : "not");const auto [type, refreshRate] = info->getRefreshRate(now);// Skip NoVote layer as those don't have any requirementsif (type == LayerHistory::LayerVoteType::NoVote) {continue;}// Compute the layer's position on the screenconst Rect bounds = Rect(strong->getBounds());const ui::Transform transform = strong->getTransform();constexpr bool roundOutwards = true;Rect transformed = transform.transform(bounds, roundOutwards);const float layerArea = transformed.getWidth() * transformed.getHeight();float weight = mDisplayArea ? layerArea / mDisplayArea : 0.0f;summary.push_back({strong->getName(), type, refreshRate, weight, layerFocused});if (CC_UNLIKELY(mTraceEnabled)) {trace(layer, *info, type, static_cast<int>(std::round(refreshRate)));}}return summary;
}
summarize中又会调用partitionLayers。首先获取一个阀值,这个阀值范围是从当前开始算往前推1200ms。然后while循环所有的active的layer,如果这个layer还存在并且在过去的1200ms内是active的再并且这个layer设置的rate不能为0再再并且layer的framerate type不能是NoVote再再再layer是可见的才会用这个layer设置的framerate去投票。不满足条件的lyaer info被设置忽略或者重置掉。之后再清理掉不需要投票的layer。
void LayerHistoryV2::partitionLayers(nsecs_t now) {const nsecs_t threshold = getActiveLayerThreshold(now);// Collect expired and inactive layers after active layers.size_t i = 0;while (i < mActiveLayersEnd) {auto& [weak, info] = mLayerInfos[i];if (const auto layer = weak.promote(); layer && isLayerActive(*layer, *info, threshold)) {i++;// Set layer vote if setconst auto frameRate = layer->getFrameRateForLayerTree();const auto voteType = [&]() {switch (frameRate.type) {case Layer::FrameRateCompatibility::Default:return LayerVoteType::ExplicitDefault;case Layer::FrameRateCompatibility::ExactOrMultiple:return LayerVoteType::ExplicitExactOrMultiple;case Layer::FrameRateCompatibility::NoVote:return LayerVoteType::NoVote;}}();if (frameRate.rate > 0 || voteType == LayerVoteType::NoVote) {const auto type = layer->isVisible() ? voteType : LayerVoteType::NoVote;info->setLayerVote(type, frameRate.rate);} else {info->resetLayerVote();}continue;}if (CC_UNLIKELY(mTraceEnabled)) {trace(weak, *info, LayerHistory::LayerVoteType::NoVote, 0);}info->onLayerInactive(now);std::swap(mLayerInfos[i], mLayerInfos[--mActiveLayersEnd]);}// Collect expired layers after inactive layers.size_t end = mLayerInfos.size();while (i < end) {if (mLayerInfos[i].first.promote()) {i++;} else {std::swap(mLayerInfos[i], mLayerInfos[--end]);}}mLayerInfos.erase(mLayerInfos.begin() + static_cast<long>(end), mLayerInfos.end());
}
接下来再回到summarize函数。首先是调用getFrameRateSelectionPriority获取每个layer设置的refresh rate的优先级。代码比较简单就是从mDrawingState和parent中获取layer设置的优先级,从判断获取优先级的逻辑可以看出来,被设置的优先级想要起作用一定是在layer 送显之后。
int32_t Layer::getFrameRateSelectionPriority() const {// Check if layer has priority set.if (mDrawingState.frameRateSelectionPriority != PRIORITY_UNSET) {return mDrawingState.frameRateSelectionPriority;}// If not, search whether its parents have it set.sp<Layer> parent = getParent();if (parent != nullptr) {return parent->getFrameRateSelectionPriority();}return Layer::PRIORITY_UNSET;
}
既然看到了获取layer设置的优先级,那就看一下上层是如何设置frame rate优先级的。这里只看native层设置优先级。调用的地方是SurfaceComposerClient中。上层通过surfacecontrol来设置layer的属性,基本上全部都是调用SurfaceComposerClient接口来实现的,这里设置layer的frame rate优先级也不例外。
SurfaceComposerClient::Transaction&
SurfaceComposerClient::Transaction::setFrameRateSelectionPriority(const sp<SurfaceControl>& sc,int32_t priority) {layer_state_t* s = getLayerState(sc);if (!s) {mStatus = BAD_INDEX;return *this;}s->what |= layer_state_t::eFrameRateSelectionPriority;s->frameRateSelectionPriority = priority;registerSurfaceControlForCallback(sc);return *this;
}
然后就是通过sf的setClientStateLocked调用到layer的setFrameRateSelectionPriority。逻辑也很简单,和设置layer的各种属性一样。
bool Layer::setFrameRateSelectionPriority(int32_t priority) {if (mCurrentState.frameRateSelectionPriority == priority) return false;mCurrentState.frameRateSelectionPriority = priority;mCurrentState.sequence++;mCurrentState.modified = true;setTransactionFlags(eTransactionNeeded);return true;
}
优先级分为4种,0最高,-1最低。
最高的优先级就是有焦点的窗口并且要投票给某一个mode。
次一级的是有焦点但是没有要设置某一个mode,基本上大部分的优先级都是这种。
再次一等的优先级就是没有焦点的窗口但是又设置了mode。
static constexpr int32_t PRIORITY_UNSET = -1;// Windows that are in focus and voted for the preferred mode IDstatic constexpr int32_t PRIORITY_FOCUSED_WITH_MODE = 0;// // Windows that are in focus, but have not requested a specific mode ID.static constexpr int32_t PRIORITY_FOCUSED_WITHOUT_MODE = 1;// Windows that are not in focus, but voted for a specific mode ID.static constexpr int32_t PRIORITY_NOT_FOCUSED_WITH_MODE = 2;
getRefreshRate就是获取每个layer info的投票类型和fps。代码逻辑很简答,这里就略过,有点特殊的就是isFrequent,这是判断图层更新的频率,如果有效记录少于90帧,有效帧的时间跨度小于1秒,都算是请求不够频繁,会被return 掉,不会被用作启发式计算的数据。最重要的就是calculateRefreshRateIfPossible。
std::pair<LayerHistory::LayerVoteType, float> LayerInfoV2::getRefreshRate(nsecs_t now) {if (mLayerVote.type != LayerHistory::LayerVoteType::Heuristic) {ALOGV("%s voted %d ", mName.c_str(), static_cast<int>(mLayerVote.type));return {mLayerVote.type, mLayerVote.fps};}if (isAnimating(now)) {ALOGV("%s is animating", mName.c_str());mLastRefreshRate.animatingOrInfrequent = true;return {LayerHistory::LayerVoteType::Max, 0};}if (!isFrequent(now)) {ALOGV("%s is infrequent", mName.c_str());mLastRefreshRate.animatingOrInfrequent = true;return {LayerHistory::LayerVoteType::Min, 0};}// If the layer was previously tagged as animating or infrequent, we clear// the history as it is likely the layer just changed its behavior// and we should not look at stale dataif (mLastRefreshRate.animatingOrInfrequent) {clearHistory(now);}auto refreshRate = calculateRefreshRateIfPossible(now);if (refreshRate.has_value()) {ALOGV("%s calculated refresh rate: %.2f", mName.c_str(), refreshRate.value());return {LayerHistory::LayerVoteType::Heuristic, refreshRate.value()};}ALOGV("%s Max (can't resolve refresh rate)", mName.c_str());return {LayerHistory::LayerVoteType::Max, 0};
}
calculateRefreshRateIfPossible开始是先判断是否有足够的数据用来做启发式计算。满足条件才能继续往下走。然后就是根据保存的数据计算出帧率。
std::optional<float> LayerInfoV2::calculateRefreshRateIfPossible(nsecs_t now) {static constexpr float MARGIN = 1.0f; // 1Hzif (!hasEnoughDataForHeuristic()) {ALOGV("Not enough data");return std::nullopt;}const auto averageFrameTime = calculateAverageFrameTime();if (averageFrameTime.has_value()) {const auto refreshRate = 1e9f / *averageFrameTime;const bool refreshRateConsistent = mRefreshRateHistory.add(refreshRate, now);if (refreshRateConsistent) {const auto knownRefreshRate =sRefreshRateConfigs->findClosestKnownFrameRate(refreshRate);// To avoid oscillation, use the last calculated refresh rate if it is// close enoughif (std::abs(mLastRefreshRate.calculated - refreshRate) > MARGIN &&mLastRefreshRate.reported != knownRefreshRate) {mLastRefreshRate.calculated = refreshRate;mLastRefreshRate.reported = knownRefreshRate;}ALOGV("%s %.2fHz rounded to nearest known frame rate %.2fHz", mName.c_str(),refreshRate, mLastRefreshRate.reported);} else {ALOGV("%s Not stable (%.2fHz) returning last known frame rate %.2fHz", mName.c_str(),refreshRate, mLastRefreshRate.reported);}}return mLastRefreshRate.reported == 0 ? std::nullopt: std::make_optional(mLastRefreshRate.reported);
}
再继续回去看这个函数summarize。获取完帧率之后就算根据图层的大小计算权重。计算方法也很简单就是用layer的大小/display的大小,这个mDisplayArea会在主屏被添加之后被设置,大小就是屏幕大小。然后就是把图层的名称,权重,刷新率等信息保存到一个vector中。接下来就是calculateRefreshRateConfigIndexType函数,这个函数判断各种会影响帧率的因素并且返回configid。在看具体代码之前先要看这两部分,一部分和mIdleTimer, mTouchTimer, mDisplayPowerTimer相关。
// Timer that records time between requests for next vsync.std::optional<scheduler::OneShotTimer> mIdleTimer;// Timer used to monitor touch events.std::optional<scheduler::OneShotTimer> mTouchTimer;// Timer used to monitor display power mode.std::optional<scheduler::OneShotTimer> mDisplayPowerTimer;
初始化是在Scheduler的构造函数中。分别初始化了mIdleTimer, mTouchTimer, mDisplayPowerTimer。就拿mTouchTimer看一下。
const int setIdleTimerMs = property_get_int32("debug.sf.set_idle_timer_ms", 0);if (const auto millis = setIdleTimerMs ? setIdleTimerMs : set_idle_timer_ms(0); millis > 0) {const auto callback = mSupportKernelTimer ? &Scheduler::kernelIdleTimerCallback: &Scheduler::idleTimerCallback;mIdleTimer.emplace(std::chrono::milliseconds(millis),[this, callback] { std::invoke(callback, this, TimerState::Reset); },[this, callback] { std::invoke(callback, this, TimerState::Expired); });mIdleTimer->start();
}if (const int64_t millis = set_touch_timer_ms(0); millis > 0) {// Touch events are coming to SF every 100ms, so the timer needs to be higher than thatmTouchTimer.emplace(std::chrono::milliseconds(millis),[this] { touchTimerCallback(TimerState::Reset); },[this] { touchTimerCallback(TimerState::Expired); });mTouchTimer->start();
}if (const int64_t millis = set_display_power_timer_ms(0); millis > 0) {mDisplayPowerTimer.emplace(std::chrono::milliseconds(millis),[this] { displayPowerTimerCallback(TimerState::Reset); },[this] { displayPowerTimerCallback(TimerState::Expired); });mDisplayPowerTimer->start();
}
OneShotTimer的start内容很简单,就是设置状态以及创建线程。线程中会调用loop函数。
void OneShotTimer::start() {{std::lock_guard<std::mutex> lock(mMutex);mState = TimerState::RESET;}mThread = std::thread(&OneShotTimer::loop, this);
}
loop函数就是调度的核心了,每一次reset之后通过mResetCallback回调重置状态,WAITING的这个while等待超时,如果超时了就调用mTimeoutCallback回掉。
void OneShotTimer::loop() {while (true) {bool triggerReset = false;bool triggerTimeout = false;{std::lock_guard<std::mutex> lock(mMutex);if (mState == TimerState::STOPPED) {break;}if (mState == TimerState::IDLE) {mCondition.wait(mMutex);continue;}if (mState == TimerState::RESET) {triggerReset = true;}}if (triggerReset && mResetCallback) {mResetCallback();}{ // lock the mutex again. someone might have called stop meanwhilestd::lock_guard<std::mutex> lock(mMutex);if (mState == TimerState::STOPPED) {break;}auto triggerTime = std::chrono::steady_clock::now() + mInterval;mState = TimerState::WAITING;while (mState == TimerState::WAITING) {constexpr auto zero = std::chrono::steady_clock::duration::zero();auto waitTime = triggerTime - std::chrono::steady_clock::now();if (waitTime > zero) mCondition.wait_for(mMutex, waitTime);if (mState == TimerState::RESET) {triggerTime = std::chrono::steady_clock::now() + mInterval;mState = TimerState::WAITING;} else if (mState == TimerState::WAITING &&(triggerTime - std::chrono::steady_clock::now()) <= zero) {triggerTimeout = true;mState = TimerState::IDLE;}}}if (triggerTimeout && mTimeoutCallback) {mTimeoutCallback();}}
}
最终通过回调调用到getBestRefreshRate函数。这个函数就是最终给出refreshrate的地方。之前各种判断是否有touch,是否idle,还有权重,投票的图层数量多少等等都会在这里拿出来计算,最终计算出最适合当前场景应该的refresh rate。
const RefreshRate& RefreshRateConfigs::getBestRefreshRate(const std::vector<LayerRequirement>& layers, const GlobalSignals& globalSignals,GlobalSignals* outSignalsConsidered) const {ATRACE_CALL();ALOGV("getRefreshRateForContent %zu layers", layers.size());if (outSignalsConsidered) *outSignalsConsidered = {};const auto setTouchConsidered = [&] {if (outSignalsConsidered) {outSignalsConsidered->touch = true;}};const auto setIdleConsidered = [&] {if (outSignalsConsidered) {outSignalsConsidered->idle = true;}};std::lock_guard lock(mLock);int noVoteLayers = 0;int minVoteLayers = 0;int maxVoteLayers = 0;int explicitDefaultVoteLayers = 0;int explicitExactOrMultipleVoteLayers = 0;float maxExplicitWeight = 0;for (const auto& layer : layers) {if (layer.vote == LayerVoteType::NoVote) {noVoteLayers++;} else if (layer.vote == LayerVoteType::Min) {minVoteLayers++;} else if (layer.vote == LayerVoteType::Max) {maxVoteLayers++;} else if (layer.vote == LayerVoteType::ExplicitDefault) {explicitDefaultVoteLayers++;maxExplicitWeight = std::max(maxExplicitWeight, layer.weight);} else if (layer.vote == LayerVoteType::ExplicitExactOrMultiple) {explicitExactOrMultipleVoteLayers++;maxExplicitWeight = std::max(maxExplicitWeight, layer.weight);}}const bool hasExplicitVoteLayers =explicitDefaultVoteLayers > 0 || explicitExactOrMultipleVoteLayers > 0;// Consider the touch event if there are no Explicit* layers. Otherwise wait until after we've// selected a refresh rate to see if we should apply touch boost.if (globalSignals.touch && !hasExplicitVoteLayers) {ALOGV("TouchBoost - choose %s", getMaxRefreshRateByPolicyLocked().getName().c_str());setTouchConsidered();return getMaxRefreshRateByPolicyLocked();}// If the primary range consists of a single refresh rate then we can only// move out the of range if layers explicitly request a different refresh// rate.const Policy* policy = getCurrentPolicyLocked();const bool primaryRangeIsSingleRate = policy->primaryRange.min == policy->primaryRange.max;if (!globalSignals.touch && globalSignals.idle &&!(primaryRangeIsSingleRate && hasExplicitVoteLayers)) {ALOGV("Idle - choose %s", getMinRefreshRateByPolicyLocked().getName().c_str());setIdleConsidered();return getMinRefreshRateByPolicyLocked();}if (layers.empty() || noVoteLayers == layers.size()) {return getMaxRefreshRateByPolicyLocked();}// Only if all layers want Min we should return Minif (noVoteLayers + minVoteLayers == layers.size()) {ALOGV("all layers Min - choose %s", getMinRefreshRateByPolicyLocked().getName().c_str());return getMinRefreshRateByPolicyLocked();}// Find the best refresh rate based on scorestd::vector<std::pair<const RefreshRate*, float>> scores;scores.reserve(mAppRequestRefreshRates.size());for (const auto refreshRate : mAppRequestRefreshRates) {scores.emplace_back(refreshRate, 0.0f);}for (const auto& layer : layers) {ALOGV("Calculating score for %s (%s, weight %.2f)", layer.name.c_str(),layerVoteTypeString(layer.vote).c_str(), layer.weight);if (layer.vote == LayerVoteType::NoVote || layer.vote == LayerVoteType::Min) {continue;}auto weight = layer.weight;for (auto i = 0u; i < scores.size(); i++) {bool inPrimaryRange =scores[i].first->inPolicy(policy->primaryRange.min, policy->primaryRange.max);if ((primaryRangeIsSingleRate || !inPrimaryRange) &&!(layer.focused && layer.vote == LayerVoteType::ExplicitDefault)) {// Only focused layers with ExplicitDefault frame rate settings are allowed to score// refresh rates outside the primary range.continue;}// If the layer wants Max, give higher score to the higher refresh rateif (layer.vote == LayerVoteType::Max) {const auto ratio = scores[i].first->fps / scores.back().first->fps;// use ratio^2 to get a lower score the more we get further from peakconst auto layerScore = ratio * ratio;ALOGV("%s (Max, weight %.2f) gives %s score of %.2f", layer.name.c_str(), weight,scores[i].first->name.c_str(), layerScore);scores[i].second += weight * layerScore;continue;}const auto displayPeriod = scores[i].first->hwcConfig->getVsyncPeriod();const auto layerPeriod = round<nsecs_t>(1e9f / layer.desiredRefreshRate);if (layer.vote == LayerVoteType::ExplicitDefault) {const auto layerScore = [&]() {// Find the actual rate the layer will render, assuming// that layerPeriod is the minimal time to render a frameauto actualLayerPeriod = displayPeriod;int multiplier = 1;while (layerPeriod > actualLayerPeriod + MARGIN_FOR_PERIOD_CALCULATION) {multiplier++;actualLayerPeriod = displayPeriod * multiplier;}return std::min(1.0f,static_cast<float>(layerPeriod) /static_cast<float>(actualLayerPeriod));}();ALOGV("%s (ExplicitDefault, weight %.2f) %.2fHz gives %s score of %.2f",layer.name.c_str(), weight, 1e9f / layerPeriod, scores[i].first->name.c_str(),layerScore);scores[i].second += weight * layerScore;continue;}if (layer.vote == LayerVoteType::ExplicitExactOrMultiple ||layer.vote == LayerVoteType::Heuristic) {const auto layerScore = [&] {// Calculate how many display vsyncs we need to present a single frame for this// layerconst auto [displayFramesQuot, displayFramesRem] =getDisplayFrames(layerPeriod, displayPeriod);static constexpr size_t MAX_FRAMES_TO_FIT =10; // Stop calculating when score < 0.1if (displayFramesRem == 0) {// Layer desired refresh rate matches the display rate.return 1.0f;}if (displayFramesQuot == 0) {// Layer desired refresh rate is higher the display rate.return (static_cast<float>(layerPeriod) /static_cast<float>(displayPeriod)) *(1.0f / (MAX_FRAMES_TO_FIT + 1));}// Layer desired refresh rate is lower the display rate. Check how well it fits// the cadenceauto diff = std::abs(displayFramesRem - (displayPeriod - displayFramesRem));int iter = 2;while (diff > MARGIN_FOR_PERIOD_CALCULATION && iter < MAX_FRAMES_TO_FIT) {diff = diff - (displayPeriod - diff);iter++;}return 1.0f / iter;}();ALOGV("%s (%s, weight %.2f) %.2fHz gives %s score of %.2f", layer.name.c_str(),layerVoteTypeString(layer.vote).c_str(), weight, 1e9f / layerPeriod,scores[i].first->name.c_str(), layerScore);scores[i].second += weight * layerScore;continue;}}}// Now that we scored all the refresh rates we need to pick the one that got the highest score.// In case of a tie we will pick the higher refresh rate if any of the layers wanted Max,// or the lower otherwise.const RefreshRate* bestRefreshRate = maxVoteLayers > 0? getBestRefreshRate(scores.rbegin(), scores.rend()): getBestRefreshRate(scores.begin(), scores.end());if (primaryRangeIsSingleRate) {// If we never scored any layers, then choose the rate from the primary// range instead of picking a random score from the app range.if (std::all_of(scores.begin(), scores.end(),[](std::pair<const RefreshRate*, float> p) { return p.second == 0; })) {ALOGV("layers not scored - choose %s",getMaxRefreshRateByPolicyLocked().getName().c_str());return getMaxRefreshRateByPolicyLocked();} else {return *bestRefreshRate;}}// Consider the touch event if there are no ExplicitDefault layers. ExplicitDefault are mostly// interactive (as opposed to ExplicitExactOrMultiple) and therefore if those posted an explicit// vote we should not change it if we get a touch event. Only apply touch boost if it will// actually increase the refresh rate over the normal selection.const RefreshRate& touchRefreshRate = getMaxRefreshRateByPolicyLocked();if (globalSignals.touch && explicitDefaultVoteLayers == 0 &&bestRefreshRate->fps < touchRefreshRate.fps) {setTouchConsidered();ALOGV("TouchBoost - choose %s", touchRefreshRate.getName().c_str());return touchRefreshRate;}return *bestRefreshRate;
}
使用API以及验证
上面那么多全部都是代码层面的分析,写两个app测试一下。
先分别写app调用setFrameRate和preferredDisplayModeId。看看是否真的像API中说的那样,preferredDisplayModeId设置之后setFrameRate设置的值就无效了。
我这边用来测试的机器,高刷是120hz的,所以判断里面获取到mode值判断是否大于61。因为这个getRefreshRate获取到的值可能是60.xxx并不一定是60。
public void btn_setpreferredDisplayModeId(View v) {Display display = this.getDisplay();Display.Mode[] modes = display.getSupportedModes();for (Display.Mode m : modes) {Log.e(TAG, "btn_setpreferredDisplayModeId: " + m.getRefreshRate());if (m.getRefreshRate() >= 61) {WindowManager.LayoutParams param = getWindow().getAttributes();param.preferredDisplayModeId = m.getModeId();getWindow().setAttributes(param);}}
}
这是调用setFrameRate的代码。
public void surfaceCreated(SurfaceHolder holder) {holder.getSurface().setFrameRate(60, FRAME_RATE_COMPATIBILITY_DEFAULT);
}
假设调用setFrameRate的APP叫做A,调用preferredDisplayModeId的APP叫做B。
我作的对比很简单,先运行B,但是不点击B中设置刷新率的按钮,然后将当前的window设置成multi-window或者pop-window,再运行A。
这个时候因为我点击了屏幕,所以刷新率会维持在120hz,查看当前的刷新率很简单,1是在开发者模式中“show refresh rate”,2是dumpsys SurfaceFlinger,从dump的信息中可以看到当前的刷新率是多少。
当一段时间不触碰屏幕之后,刷新率会降低到60hz。这就是A设置的刷新率。这时候我再点击B中的按钮,调用preferredDisplayModeId设置到120hz。这时候刷新率就会变成120hz,之后无论怎么操作,只要B的window还在(relaunch之后要重新点击button)刷新率会一直在120hz,就算你重新关闭打开A。
这是一个对比,两种设置刷新率方式的对比。另外还有一个对比,就是一个app设120hz,一个app设60fps,到底会怎么样呢?
经过测试发现,这和app的focus相关。假设设置60hz的app叫做app_60,另外一个叫做app_120,当焦点在app_60上的时候,启发式计算就会将刷新率设置为60,反之则会设置为120.原因在上面的代码分析中也写过了,和window的几种级别相关。
还有一些别的场景,我也不想去一一做对比了,有兴趣的可以自己测试。以上两种测试只是为了分析完代码之后写个APP印证一下。
android R Variable Refresh Rate 可变帧率 VRR相关推荐
- Android中可变帧率VRR
Android 从Q开始实现对可变帧率(VRR:Variable Refresh Rate)的支持.本文以android Q 为基础介绍android VRR的实现, 与android S有部分差别, ...
- android动态修改refresh rate,Lenovo k860i 移植Android 4.4 cm11进度记录【下篇--实时更新中】...
2014.8.24 k860i的cm11的移植在中断了近两三个月之后又开始继续了,进度记录的日志上一篇已经没什么写的了,就完结掉它吧,重新开一篇日志做下篇好了.最近的战况是,在scue同学的努力之下, ...
- Android 10 (Android Q)中的屏幕刷新率(display refresh rate)切换方法和策略
本文禁止转载,如有需求,请联系作者. 1. 屏幕刷新率和应用的显示帧率 首先请区分好屏幕刷新率(Panel Refresh Rate)和应用的帧率(APP Frame Rate)两个概念 ...
- Android: R cannot be resolved to a variable
Android开发过程中,碰到R cannot be resolved to a variable的报错信息,网上的解决办法汇总: 首先说下我自己的问题所在,尝试过网上的各种方法,均未奏效,最后发现是 ...
- 多屏幕刷新率(refresh rate)模式下的帧显示流水线
给Android应用和游戏开发工程师的最新参考. 长时间以来,手机显示屏幕的刷新率一直是固定的60Hz. 从2019年开始,由于游戏等应用对响应速度的需求越来越高,越来越多的手机开始在高刷新率(90/ ...
- android r中的变量_R中的变量
android r中的变量 Variables in R are the same as the notion of variables in any other programming langua ...
- Android 自带图标库 android.R.drawable
在xml文件中调用. android:title="@string/secure_connect" android:orderInCategory="100" ...
- 视频编码中的RC(rate control)是什么?码率控制 CBR (Constant Bit Rate)、VBR (Variable Bit Rate)
再看RXW源码的时候在视频编码结构体里有一个RC结构体,RC(rate control)到底是啥? 码率控制技术 RC (Rate Control) 是实际视频编码器中一个非常重要的技术模块. 根据实 ...
- Variable Assembly Language可变汇编语言
Variable Assembly Language可变汇编语言 可变汇编语言(Variable Assembly Language, VAL)是一个设计给Unimation Inc.工业机器人用的电 ...
最新文章
- linux vi文本类常用命令
- Redis的两种消息模式
- 50佳设计独特的名片设计欣赏(上篇)
- Angular 应用的DevDependencies
- 利用ado.net和winform连接ms Server可以做什么?
- JavaScript算法(实例八)递归计算每个月的兔子总数【斐波那契数列】
- java 线程 主进程_java 多线程通用方法
- [2003] Can't connect to MySQL server on 'localhost' (13) 错误解决方法 【主要是linux下php连接不上mysq时】...
- ubuntu16.04 安装 wxPython方法
- linux下交叉编译ffmpeg,并加入H264编码支持
- 解析java文件_使用JDK的Parser来解析Java源代码
- zend studio php调试,Zend Studio中如何配置和使用xdebug断点调试工具?
- 问题解决之——未知usb设备设备描述符请求失败(Jlink驱动)
- JSP介绍及视频教程
- SXF 安全服务一面
- jbx添加加mysql驱动
- Web3.0:Skiff 去中心化隐私协同文档
- 关于STM8的程序下载问题:SWIM Error[30006]报错解决办法汇总
- 51单片机 普中V2 数字时钟 电子时钟 万年历 DS1302 LCD1602 AT24C02
- 计算机怎么复制公式,excel怎么复制公式 -电脑资料
热门文章
- 热像仪的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告
- react setState特性
- CSS初始化的两种常见方法
- root登录Linux图形界面
- GitLab删除项目的操作步骤
- java crc计算_JavaCRC校验原理
- 知识蒸馏论文翻译(7)—— Knowledge Distillation from Internal Representations(内部表征)
- Android 日期筛选器
- pc android系统下载,在PC上运行Android-x86 9.0-r1进行下载
- 计算机应用中最高语言,计算机应用基础考试题