blog

Android R 可変リフレッシュレート 可変フレームレート研究

可変リフレッシュレートの紹介\nAndroid RでGoogleが追加したディスプレイ機能:VRR0.5。\n\nAndroidのコードを見ると、GoogleはQからVRR0.5機能に取り組んでいるこ...

Jun 19, 2020 · 22 min. read
シェア

Variable Refresh Rate

Android RGoogleが追加したディスプレイ機能:VRR0.5

早くもGoogleのように、AndroidのOEMは、ワンプラス7プロのリリースは、90HZの高リフレッシュの時代に業界をもたらしたまで、可変フレームレートを研究開発し始めています。高リフレッシュは必然的に巨大な消費電力の増加をもたらすでしょう、同時に、ビデオゲームや他のアプリケーションは、映画のソースや適応やその他の問題のため、高フレームレートの値を反映していないはずです。これらのケースのOEMは、可変フレームレート戦略とアプリケーションの逆適応の多くを行っています。しかし、異なるメーカーの戦略は大きく異なり、アプリケーションが一致しない、統一された完全なハイフレームエコシステムの形成がありませんでした。

Androidのコードを見ると、GoogleはQから可変フレームレートのシナリオに取り組んでおり、統一された共通の可変フレームレートメカニズムを提供しようとしていることがわかります:

  • アプリケーションがレイヤーに必要なフレームを指定できるようにします。
  • ディスプレイのリフレッシュレートを決定します。
  • SDKそしてNDKはインターフェイスを提供します

WMSのインターフェースとポリシーの紹介

New public APIs

  • Surface.setFrameRate()
  • SurfaceControl.Transaction.setFrameRate()
  • ANativeWindow_setFrameRate()
  • ASurfaceTransaction_setFrameRate()

パラメーター

  • Video apps - VideoまたはGameレイヤーのフレームレートを指定します。FRAME_RATE_COMPATIBILITY_FIXED_SOURCE ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE
  • ビデオ以外のアプリFRAME_RATE_COMPATIBILITY_DEFAULT ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT

WMSにおけるインターフェースの実装とポリシー

setFrameRate

Android Rは、ビデオのようなアプリケーションのリフレッシュレートにシステムが適応しないと、ユーザーエクスペリエンスが低下するという事実を考慮しています。そのため、Android Rは、個々のレイヤーのフレームレートを設定するインターフェースを提供しています。

Surface.java & SurfaceControl.java setFrameRateメソッドはすでに提供されています。

パラメータ frameRate = 0 は、システムのデフォルトフレームレートが使用されることを意味します。

 /** @hide */
 @Retention(RetentionPolicy.SOURCE)
 @IntDef(prefix = {"FRAME_RATE_COMPATIBILITY_"},
 value = {FRAME_RATE_COMPATIBILITY_DEFAULT, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE})
 public @interface FrameRateCompatibility {}

setFrameRateSelectionPriority - フレームレートの優先順位付け戦略

SurfaceControl.javaはフレームレートの選択優先度を設定するインターフェイスを持っています。SFはウィンドウの優先順位を通知されます。 SF はこの情報を使用して、画面のリフレッシュ レートを決定するときに、どのウィンドウの希望するレンダリング レートを優先するかを決定します。

デフォルトでは、すべてのウィンドウの優先順位は最も低くなっています。

そうでなければ、アプリケーションに悪用されやすくなります。

Android RのWMSは、関連するポリシーであるRefreshRatePolicyをすでに実行しています。

優先順位の計算方法

  • 0はデフォルトで最高の優先度、-1は優先度が設定されていないことを意味します。
  • 希望のモードIDに投票する場合、フォーカスのあるウィンドウが最優先されます。
  • フォーカスはあるがモードが指定されていないウィンドウがデフォルトの優先順位です。
  • ウィンドウはありませんが、指定されたモードウィンドウがあり、その優先順位は最も低くなっています。
  • これは分割画面タイプのシナリオ用です。例えば、分割画面の下に2つのウィンドウがあり、1つは現在のシステムフレームレートではなくフォーカスがあり、1つはモードを形成するためのフォーカスがない場合、この場合、指定されたモードを持つウィンドウにモードを設定することができます。

さらに、WMSに2つのクラスリストが追加され、より多くの状況に対応できるようになりました。

// ハイフレームでないアプリケーションのパッケージ名
private final ArraySet<String> mNonHighRefreshRatePackages = new ArraySet<>();
// ブラックリスト、これはブラックリスト内のパッケージ名に対して高いフレームの使用を制限する。
private final HighRefreshRateBlacklist mHighRefreshRateBlacklist;
高フレームのブラックリスト

HighRefreshRateBlacklist クラスはハイフレームのブラックリストを管理し、更新ロジックと判定ロジックを提供します。

HighRefreshRateBlacklist(Resources r, DeviceConfigInterface deviceConfig) {
 mDefaultBlacklist = r.getStringArray(R.array.config_highRefreshRateBlacklist);
 ... ...
 }

配列内のブラックリストを読み込みます。

SurfaceFlingerの実装ロジックは

Layer.setFrameRateSelectionPriority && Layer.setFrameRate

config_highRefreshRateBlacklist トップレイヤーはボトムレイヤーにも対応しています。

上位レイヤーのsetFrameRateは、下位レイヤーのsetFrameRateに対応します。

レイヤー.cpp

 struct State {
 ... ...
 // setFrameRateSelectionPriority設定に対応するパラメータ
 // Priority of the layer assigned by Window Manager.
 int32_t frameRateSelectionPriority;
 // setFrameRate設定に対応するパラメータ
 FrameRate frameRate;
 // Indicates whether parents / children of this layer had set FrameRate
 bool treeHasFrameRateVote;
 };
 // Encapsulates the frame rate and compatibility of the layer. 
 // This information will be used
 // when the display refresh rate is determined.
 struct FrameRate {
 float rate;
 FrameRateCompatibility type;
 ... ...
 };

FrameRateCompatibility

 // FrameRateCompatibility specifies how we should interpret the frame rate associated with
 // the layer.
 enum class FrameRateCompatibility {
 Default, // Layer didn't specify any specific handling strategy
 ExactOrMultiple, // Layer needs the exact frame rate (or a multiple of it) to present the
 // content properly. Any other value will result in a pull down.
 NoVote, // Layer doesn't have any requirements for the refresh rate and
 // should not be considered when the display refresh rate is determined.
 };

FrameRateCompatibilityFrameRateCompatibility 上のレイヤーはSurfaceFlingerに対応しています:

  • FRAME_RATE_COMPATIBILITY_DEFAULT == Default
  • FRAME_RATE_COMPATIBILITY_FIXED_SOURCE == ExactOrMultiple
Layer::FrameRateCompatibility Layer::FrameRate::convertCompatibility(int8_t compatibility) {
 switch (compatibility) {
 case ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT:
 return FrameRateCompatibility::Default;
 case ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE:
 return FrameRateCompatibility::ExactOrMultiple;
 default:
 LOG_ALWAYS_FATAL("Invalid frame rate compatibility value %d", compatibility);
 return FrameRateCompatibility::Default;
 }
}

frameRateSelectionPriority Android Rのコードを確認したところ、このパラメータSFは使用されていません。

// TODO(b/144307188): This needs to be plugged into layer summary as
// an additional parameter.
ALOGV("Layer has priority: %d", strong->getFrameRateSelectionPriority());

レイヤー履歴統計 - ヒューリスティック計算用

レイヤが必要なフレームレートを指定していない場合、現在のレイヤのフレームレートは、過去のデータを計算することにより、ヒューリスティックスを使用して推定されます。

void LayerHistoryV2::record(Layer* layer, nsecs_t presentTime, nsecs_t now) {
 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);
 // Activate layer if inactive.
 if (const auto end = activeLayers().end(); it >= end) {
 std::iter_swap(it, end);
 mActiveLayersEnd++;
 }
}
void LayerInfoV2::setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now) {
 lastPresentTime = std::max(lastPresentTime, static_cast<nsecs_t>(0));
 mLastUpdatedTime = std::max(lastPresentTime, now);
 FrameTimeData frameTime = {.presetTime = lastPresentTime, .queueTime = mLastUpdatedTime};
 mFrameTimes.push_back(frameTime);
 if (mFrameTimes.size() > HISTORY_SIZE) {
 mFrameTimes.pop_front();
 }
}

レイヤーの更新が頻繁かどうかの判断

bool LayerInfoV2::isFrequent(nsecs_t now) const {
 // Find the first valid frame time
 auto it = mFrameTimes.begin();
 for (; it != mFrameTimes.end(); ++it) {
 if (isFrameTimeValid(*it)) {
 break;
 }
 }
 // If we know nothing about this layer we consider it as frequent as it might be the start
 // of an animation.
 if (std::distance(it, mFrameTimes.end()) < FREQUENT_LAYER_WINDOW_SIZE) {
 return true;
 }
 // Find the first active frame
 for (; it != mFrameTimes.end(); ++it) {
 if (it->queueTime >= getActiveLayerThreshold(now)) {
 break;
 }
 }
 const auto numFrames = std::distance(it, mFrameTimes.end());
 if (numFrames < FREQUENT_LAYER_WINDOW_SIZE) {
 return false;
 }
 // Layer is considered frequent if the average frame rate is higher than the threshold
 const auto totalTime = mFrameTimes.back().queueTime - it->queueTime;
 return (1e9f * (numFrames - 1)) / totalTime >= MIN_FPS_FOR_FREQUENT_LAYER;
}

レイヤーのリフレッシュレートを計算

std::optional<float> LayerInfoV2::calculateRefreshRateIfPossible() {
 static constexpr float MARGIN = 1.0f; // 1Hz
 if (!hasEnoughDataForHeuristic()) {
 ALOGV("Not enough data");
 return std::nullopt;
 }
 // Calculate the refresh rate by finding the average delta between frames
 nsecs_t totalPresentTimeDeltas = 0;
 for (auto it = mFrameTimes.begin(); it != mFrameTimes.end() - 1; ++it) {
 // If there are no presentation timestamp provided we can't calculate the refresh rate
 if (it->presetTime == 0 || (it + 1)->presetTime == 0) {
 return std::nullopt;
 }
 // PresentTimeデルタの合計を計算する。
 totalPresentTimeDeltas +=
 std::max(((it + 1)->presetTime - it->presetTime), mHighRefreshRatePeriod);
 }
 // フレームあたりの平均時間を計算する
 const float averageFrameTime =
 static_cast<float>(totalPresentTimeDeltas) / (mFrameTimes.size() - 1);
 // Now once we calculated the refresh rate we need to make sure that all the frames we captured
 // are evenly distributed and we don't calculate the average across some burst of frames.
 // すべてのキャプチャフレームが均等になるようにバーストフレームを削除する。
 for (auto it = mFrameTimes.begin(); it != mFrameTimes.end() - 1; ++it) {
 const nsecs_t presentTimeDeltas =
 std::max(((it + 1)->presetTime - it->presetTime), mHighRefreshRatePeriod);
 if (std::abs(presentTimeDeltas - averageFrameTime) > 2 * averageFrameTime) {
 return std::nullopt;
 }
 }
 const auto refreshRate = 1e9f / averageFrameTime;
 // その差が1HZより大きい場合、フレームレートが更新される。
 if (std::abs(refreshRate - mLastReportedRefreshRate) > MARGIN) {
 mLastReportedRefreshRate = refreshRate;
 }
 ALOGV("Refresh rate: %.2f", mLastReportedRefreshRate);
 return mLastReportedRefreshRate;
}
std::pair<LayerHistory::LayerVoteType, float> LayerInfoV2::getRefreshRate(nsecs_t now) {
 // LayerVoteTypeのみ== Heuristic可能なリフレッシュレートを計算する前に
 if (mLayerVote.type != LayerHistory::LayerVoteType::Heuristic) {
 return {mLayerVote.type, mLayerVote.fps};
 }
 if (!isFrequent(now)) {
 return {LayerHistory::LayerVoteType::Min, 0};
 }
 auto refreshRate = calculateRefreshRateIfPossible();
 if (refreshRate.has_value()) {
 return {LayerHistory::LayerVoteType::Heuristic, refreshRate.value()};
 }
 return {LayerHistory::LayerVoteType::Max, 0};
}

画面更新フレームレート決定ロジック

Android Rでは、ウィンドウが分割されています:

  • wallpaper - 最小フレームレートで動作する壁紙レイヤー
  • status bar - Doesn't care about the refresh rate
  • その他のレイヤー - フレームレートのヒューリスティック推定
void Scheduler::registerLayer(Layer* layer) {
 if (!mLayerHistory) return;
 // If the content detection feature is off, all layers are registered at NoVote. We still
 // keep the layer history, since we use it for other features (like Frame Rate API), so layers
 // still need to be registered.
 if (!mUseContentDetection) {
 mLayerHistory->registerLayer(layer, mRefreshRateConfigs.getMinRefreshRate().fps,
 mRefreshRateConfigs.getMaxRefreshRate().fps,
 scheduler::LayerHistory::LayerVoteType::NoVote);
 return;
 }
 // In V1 of content detection, all layers are registered as Heuristic (unless it's wallpaper).
 if (!mUseContentDetectionV2) {
 const auto lowFps = mRefreshRateConfigs.getMinRefreshRate().fps;
 const auto highFps = layer->getWindowType() == InputWindowInfo::TYPE_WALLPAPER
 ? lowFps
 : mRefreshRateConfigs.getMaxRefreshRate().fps;
 mLayerHistory->registerLayer(layer, lowFps, highFps,
 scheduler::LayerHistory::LayerVoteType::Heuristic);
 } else {
 if (layer->getWindowType() == InputWindowInfo::TYPE_WALLPAPER) {
 // Running Wallpaper at Min is considered as part of content detection.
 mLayerHistory->registerLayer(layer, mRefreshRateConfigs.getMinRefreshRate().fps,
 mRefreshRateConfigs.getMaxRefreshRate().fps,
 scheduler::LayerHistory::LayerVoteType::Min);
 } else if (layer->getWindowType() == InputWindowInfo::TYPE_STATUS_BAR) {
 mLayerHistory->registerLayer(layer, mRefreshRateConfigs.getMinRefreshRate().fps,
 mRefreshRateConfigs.getMaxRefreshRate().fps,
 scheduler::LayerHistory::LayerVoteType::NoVote);
 } else {
 mLayerHistory->registerLayer(layer, mRefreshRateConfigs.getMinRefreshRate().fps,
 mRefreshRateConfigs.getMaxRefreshRate().fps,
 scheduler::LayerHistory::LayerVoteType::Heuristic);
 }
 }
}

レイヤーのトラバース

  • FRAME_RATE_COMPATIBILITY_FIXED_SOURCEレイヤが希望するリフレッシュレート( )を指定した場合、setLayerVoteでLayerInfoV2に通知します。LayerInfoV2がフレームレートを計算すると、それがヒューリスティックではなく、指定されたフレームレートであることがわかり、指定されたフレームレートを直接返します。
  • FRAME_RATE_COMPATIBILITY_DEFAULT( ) の場合、レイヤーで指定されたフレームレートになります。
  • そのレイヤーが投票を棄権した場合、リフレッシュレートに対する要求はなく、ディスプレイのリフレッシュレートを決定する際にそのレイヤーを考慮すべきではありません。その場合、そのレイヤーのフレームレートは他のレイヤー
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 set
 const 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) {
 info->setLayerVote(voteType, frameRate.rate);
 } else {
 info->resetLayerVote();
 }
 continue;
 }
 if (CC_UNLIKELY(mTraceEnabled)) {
 trace(weak, LayerHistory::LayerVoteType::NoVote, 0);
 }
 info->clearHistory();
 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());
}

レイヤーの重みの計算

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;
 }
 // 上位レイヤーが設定した優先順位は使用されない,TODO
 // TODO(b/144307188): This needs to be plugged into layer summary as
 // an additional parameter.
 ALOGV("Layer has priority: %d", strong->getFrameRateSelectionPriority());
 const bool recent = info->isRecentlyActive(now);
 if (recent) {
 const auto [type, refreshRate] = info->getRefreshRate(now);
 // 棄権したレイヤーは考慮されない
 // Skip NoVote layer as those don't have any requirements
 if (type == LayerHistory::LayerVoteType::NoVote) {
 continue;
 }
 // 画面内のレイヤーの位置を計算する
 // Compute the layer's position on the screen
 const 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});
 if (CC_UNLIKELY(mTraceEnabled)) {
 trace(layer, type, static_cast<int>(std::round(refreshRate)));
 }
 } else if (CC_UNLIKELY(mTraceEnabled)) {
 trace(layer, LayerHistory::LayerVoteType::NoVote, 0);
 }
 }
 return summary;
}

コンテンツのリフレッシュレートを決定

struct {
 ContentDetectionState contentDetectionV1 = ContentDetectionState::Off;
 TimerState idleTimer = TimerState::Reset;
 TouchState touch = TouchState::Inactive;
 TimerState displayPowerTimer = TimerState::Expired;
 std::optional<HwcConfigIndexType> configId;
 
 // LayerHistory 
 // using Summary = std::vector<RefreshRateConfigs::LayerRequirement>;
 /*
 struct LayerRequirement {
 std::string name; // Layer's name. Used for debugging purposes.
 LayerVoteType vote; // Layer vote type.
 float desiredRefreshRate; // Layer's desired refresh rate, if applicable.
 float weight; // Layer's weight in the range of [0, 1]. The higher the weight the more
 };
 */
 scheduler::LayerHistory::Summary contentRequirements;
 bool isDisplayPowerStateNormal = true;
} mFeatures
// コンテンツに基づいてフレームレートを選択する
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;
 // フレームレート計算ロジック
 newConfigId = calculateRefreshRateConfigIndexType();
 if (mFeatures.configId == newConfigId) {
 return;
 }
 mFeatures.configId = newConfigId;
 auto& newRefreshRate = mRefreshRateConfigs.getRefreshRateFromConfigId(newConfigId);
 mSchedulerCallback.changeRefreshRate(newRefreshRate, ConfigEvent::Changed);
 }
}

calculateRefreshRateConfigIndexType この機能は、ディスプレイのリフレッシュレートを計算する際に、さまざまなシナリオを高いものから低いものへと優先します:

  • Power
  • Touch
  • Contentリフレッシュの有無
HwcConfigIndexType Scheduler::calculateRefreshRateConfigIndexType() {
 ATRACE_CALL();
 // NOTE: If we remove the kernel idle timer, and use our internal idle timer, this
 // code will have to be refactored. If Display Power is not in normal operation we want to be in
 // performance mode. When coming back to normal mode, a grace period is given with
 // DisplayPowerTimer.
 if (mDisplayPowerTimer &&
 (!mFeatures.isDisplayPowerStateNormal ||
 mFeatures.displayPowerTimer == TimerState::Reset)) {
 return mRefreshRateConfigs.getMaxRefreshRateByPolicy().configId;
 }
 if (!mUseContentDetectionV2) {
 // As long as touch is active we want to be in performance mode.
 if (mTouchTimer && mFeatures.touch == TouchState::Active) {
 return mRefreshRateConfigs.getMaxRefreshRateByPolicy().configId;
 }
 }
 // If timer has expired as it means there is no new content on the screen.
 if (mIdleTimer && mFeatures.idleTimer == TimerState::Expired) {
 return mRefreshRateConfigs.getMinRefreshRateByPolicy().configId;
 }
 if (!mUseContentDetectionV2) {
 // If content detection is off we choose performance as we don't know the content fps.
 if (mFeatures.contentDetectionV1 == ContentDetectionState::Off) {
 // NOTE: V1 always calls this, but this is not a default behavior for V2.
 return mRefreshRateConfigs.getMaxRefreshRateByPolicy().configId;
 }
 // Content detection is on, find the appropriate refresh rate with minimal error
 return mRefreshRateConfigs.getRefreshRateForContent(mFeatures.contentRequirements).configId;
 }
 bool touchConsidered;
 // この関数で最も重要な呼び出し
 const auto& ret =
 mRefreshRateConfigs
 .getRefreshRateForContentV2(mFeatures.contentRequirements,
 mTouchTimer &&
 mFeatures.touch == TouchState::Active,
 &touchConsidered)
 .configId;
 if (touchConsidered) {
 // Clear layer history if refresh rate was selected based on touch to allow
 // the hueristic to pick up with the new rate.
 mLayerHistory->clear();
 }
 return ret;
}

isDisplayPowerStateNormal カウンターパートは HWC_POWER_MODE_NORMAL

getRefreshRateForContentV2 は、優先順位の高いシーンを除外した後の最終的なディスプレイのリフレッシュレートを計算する関数です。

mAvailableRefreshRates ここで変数はソートされます。

const RefreshRate& RefreshRateConfigs::getRefreshRateForContentV2(
 const std::vector<LayerRequirement>& layers, bool touchActive,
 bool* touchConsidered) const {
 ATRACE_CALL();
 ALOGV("getRefreshRateForContent %zu layers", layers.size());
 *touchConsidered = false;
 std::lock_guard lock(mLock);
 // If there are not layers, there is not content detection, so return the current
 // refresh rate.
 // レイヤーがない場合、コンテンツはチェックされない,
 if (layers.empty()) {
 *touchConsidered = touchActive;
 return touchActive ? *mAvailableRefreshRates.back() : getCurrentRefreshRateByPolicyLocked();
 }
 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);
 }
 }
 // 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 get get a touch event.
 // 上位のFRAMEに対応する_RATE_COMPATIBILITY_DEFAULT
 // Layer指定フレームレート
 // Androidこのようなレイヤーのネイティブ・ポリシーは、レイヤーが存在する場合、タッチイベントを取得してもフレームレートが最大まで上昇しないことである。
 if (touchActive && explicitDefaultVoteLayers == 0) {
 *touchConsidered = true;
 // mAvailableRefreshRates.back() 最大フレームレート
 return *mAvailableRefreshRates.back();
 }
 // Only if all layers want Min we should return Min
 if (noVoteLayers + minVoteLayers == layers.size()) {
 // mAvailableRefreshRates.front()最小フレームレート
 return *mAvailableRefreshRates.front();
 }
 // Find the best refresh rate based on score
 std::vector<std::pair<const RefreshRate*, float>> scores;
 // スコアベクタコンテナをmAvailableRefreshRatesと同じ大きさにする。
 scores.reserve(mAvailableRefreshRates.size());
 for (const auto refreshRate : mAvailableRefreshRates) {
 // スコア内のフレームレートに対応するすべてのスコアを0に設定する.0f
 scores.emplace_back(refreshRate, 0.0f);
 }
 for (const auto& layer : layers) {
 ALOGV("Calculating score for %s (type: %d)", layer.name.c_str(), layer.vote);
 if (layer.vote == LayerVoteType::NoVote || layer.vote == LayerVoteType::Min) {
 continue;
 }
 auto weight = layer.weight;
 for (auto i = 0u; i < scores.size(); i++) {
 // もしレイヤーが最大のフレームレートを望むなら、最も高いフレームレートに最も高い得点を与える
 // If the layer wants Max, give higher score to the higher refresh rate
 if (layer.vote == LayerVoteType::Max) {
 // scores.back().first->fps最大のフレームレートである
 // ここで、値は現在のフレームレートを最大フレームレートで割って得られる< 1
 // 最大フレームレートを最大フレームレートで割った値である。= 1
 // 四角を加えた後、最大フレームレートより小さい他のフレームレートは、最大フレームレートよりはるかに小さい得点になる。
 // このようにして、最も高い点数が最も高いフレームレートに与えられる
 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 peak
 const 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->vsyncPeriod;
 // desiredRefreshRate のLayerRequirement構造にある。
 // 前のセクションで述べたように,using Summary = std::vector<RefreshRateConfigs::LayerRequirement>;
 // Summaryこの例のrefreshRateは、LayerInfoV2のgetRefreshRate()関数に由来する。
 // この関数は以下を返す{mLayerVote.type, mLayerVote.fps};
 // mLayerVotefpsは以下のように設定される
 // info->setLayerVote(voteType, frameRate.rate);
 // FrameRateこの例でのレートは上位のsetFrameRateインターフェースから渡される。
 // だからレイヤー.desiredRefreshRate == frameRate.rate
 const auto layerPeriod = round<nsecs_t>(1e9f / layer.desiredRefreshRate);
 // 上位のFRAMEに対応する_RATE_COMPATIBILITY_DEFAULT
 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 frame
 // もしレイヤーが現在の期間より大きな期間を必要とするなら、現在のリフレッシュ期間をずっと長くする。
 auto actualLayerPeriod = displayPeriod;
 int multiplier = 1;
 while (layerPeriod > actualLayerPeriod + MARGIN_FOR_PERIOD_CALCULATION) {
 multiplier++;
 actualLayerPeriod = displayPeriod * multiplier;
 }
 // layerPeriod / actualLayerPeriodこの値には2つの可能性がある
 // 1. actualLayerPeriod < layerPeriod
 // && actualLayerPeriod + MARGIN_FOR_PERIOD_CALCULATION > layerPeriod
 // 次に、layerPeriod / actualLayerPeriodとする。> 1,このフレームレートはこのレイヤーの得点となる= 1
 // 2. actualLayerPeriod > layerPeriod
 // 次に、layerPeriod / actualLayerPeriodとする。< 1,このフレームレートはこのレイヤーの得点となる< 1
 // 見ての通り、最初のシナリオが得点の点で有利である。なぜなら、レイヤーのリフレッシュ周波数がディスプレイの周波数より大きければ、レイヤーのリフレッシュ周波数がディスプレイの周波数より高くなるからである。,
 // レイヤーのリフレッシュフレームレートがディスプレイのフレームレートより小さく、両者が近ければ、すべてのフレームが表示される。
 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;
 }
 // Layerシナリオのレイヤーコンテンツのリフレッシュレートまたは発見的に推定されたリフレッシュレートを指定する。
 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
 // layer
 // getDisplayFramesこの関数は、レイヤーのレイヤー周期/ディスプレイ周期を範囲とする。
 // 四捨五入を含む商と余り
 const auto [displayFramesQuot, displayFramesRem] =
 getDisplayFrames(layerPeriod, displayPeriod);
 static constexpr size_t MAX_FRAMES_TO_FIT =
 10; // Stop calculating when score < 0.1
 // 残りが== 0,レイヤーに要求されるリフレッシュレートはディスプレイのリフレッシュレートの整数倍である。
 // このシナリオが一致するとみなされ、そのフレームレートがこのレイヤーの得点となる= 1
 if (displayFramesRem == 0) {
 // Layer desired refresh rate matches the display rate.
 return 1.0f;
 }
 //  == 0,レイヤーに要求されるフレームレートが、現在提供されているディスプレイフレームレートよりはるかに大きいことを示す。
 // そのようなシナリオ間の差が大きいほど、スコアは低くなる
 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));
 }
 // ここに入るケースはlayerPeriodである> displayPeriod
 // しかし、layerPeriod / displayPeriodは積分できない。
 // Layer要求されるフレームレートはディスプレイのフレームレートよりはるかに小さい。
 // Layer desired refresh rate is lower the display rate. Check how well it fits
 // the cadence
 auto 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 (ExplicitExactOrMultiple, 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;
 }
 }
 }
 // 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());
 return *bestRefreshRate;
}

getBestRefreshRate 最適なリフレッシュレートの最高得点を取得します。

const RefreshRate* RefreshRateConfigs::getBestRefreshRate(Iter begin, Iter end) const {
 constexpr auto EPSILON = 0.001f;
 const RefreshRate* bestRefreshRate = begin->first;
 float max = begin->second;
 for (auto i = begin; i != end; ++i) {
 const auto [refreshRate, score] = *i;
 ALOGV("%s scores %.2f", refreshRate->name.c_str(), score);
 ATRACE_INT(refreshRate->name.c_str(), round<int>(score * 100));
 if (score > max * (1 + EPSILON)) {
 max = score;
 bestRefreshRate = refreshRate;
 }
 }
 return bestRefreshRate;
}

まとめ

上記のロジックを要約すると

  • SDKとNDKは、アプリケーションが独自のシナリオに基づいてリフレッシュフレームレートを投票できるようにするインターフェイスを公開します。
  • WMSは、フォーカス、スプリットスクリーンなどのフレームレートを優先し、ブラックリストメカニズムを提供します。
  • SurfaceFlingerは、取得した情報に基づいて表示フレームレートの最終決定を行います。
    • アプリケーションまたはフレームワークによってリフレッシュレートが設定されておらず、コンフィギュレーションでコンテンツリフレッシュレート統計ロジックがオンになっている場合、レイヤーのリフレッシュ履歴に基づいてレイヤーコンテンツのリフレッシュレートのヒューリスティック推定が行われ、ポーリングタイプが更新されます。
    • アプリケーションまたはフレームワークがリフレッシュレートを設定した場合、APPが設定したリフレッシュレートが優先され、ポーリングタイプが更新されます。
    • 各レイヤーを繰り返し、表示領域におけるレイヤーの割合を計算し、それを重みとして使用します。
    • 電源、タッチなどの特別なシナリオを考慮します。
    • 特別なシナリオがない場合、異なる LayerVoteTypes のレイヤーの数に応じてリフレッシュレートを最初に決定します。
    • 上記の決定がいずれも成功しなかった場合、すべてのレイヤーが走査され、サポートされているすべてのディスプレイフレームレートが走査されます。各ディスプレイフレームレートには、レイヤの要件に基づいたスコアが与えられます。
    • 最もスコアの高いフレームレートが全てのシーンを満足させる可能性の高いフレームレートです。

Read next

コンポーネントレンダリングのVue3.0ソースコード解析、実DOMへのvnode

このコードはページ上にラベルをレンダリングしませんが、何がレンダリングされるかはコンポーネントのテンプレートの書き方次第です。たとえば、コンポーネント内部のテンプレート定義は次のようになります。ご覧のように、テンプレートは内部的にページ上の div をレンダリングすることになり、その div には Hello World ... を表示する p タグが含まれます。

Jun 19, 2020 · 18 min read