Variable Refresh Rate (VRR) Introduction

Google added Display Feature: VRR0.5 to Android R

Android Oems had been working on Variable Refresh rates long before Google, until the release of the OnePlus 7 Pro brought the industry into the 90HZ era. High refresh inevitably brings huge incremental power consumption, while video games and other applications do not reflect the value of high frames due to issues such as source or adaptation. Oems respond to these situations with a lot of variable frame rate strategies and reverse adaptation of applications (MEMC video motion framing, etc.). However, different manufacturers’ strategies differ greatly, and applications do not cooperate, so there has been no unified and complete high frame ecology.

By looking at the Android code, we find that Google has started to divide the variable frame rate scenarios since Q, trying to provide a unified and universal variable frame rate mechanism. Another refactoring in Android R has made this feature possible – VRR0.5. Variable Refresh Rate (VRR) 0.5 provides the following functions:

  • Allows this layer to apply the required frames for the specified layer
  • Helps determine display refresh rate
  • Interfaces are provided in the SDK and NDK

Interface describes policies in WMS

New public APIs

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

Parameters:

  • Video Apps – Specify the frame rate you want, for Video or Game layersFRAME_RATE_COMPATIBILITY_FIXED_SOURCE ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE
  • Non-video apps FRAME_RATE_COMPATIBILITY_DEFAULT ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT

Implementation of interface & policies in WMS

setFrameRate

Android R already allows for applications like video, and if the system doesn’t adapt to the refresh rate of such applications, it can lead to a poor user experience (repetitive frames). As a result, Android R provides an interface for setting frame rates for individual layers.

Surface.java & Surfacecontrol.java already provides setFrameRate methods.

When frameRate = 0, the default frameRate is used. Compatibility parameters affect the frame rate decision of the system.

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

SetFrameRateSelectionPriority – frame rate priority strategy

Surfacecontrol.java has an interface for setting the framerate selection priority. Notify the priority of the SF window. SF then uses this information to determine which window’s desired rendering rate should have priority when determining the refresh rate of the screen.

By default, all Windows have the lowest priority.

This priority – related interface is best managed by WMS, otherwise it can easily be abused by applications.

Android R WMS has already done the following:RefreshRatePolicy

Priority calculation method:

  • 0 is the highest priority by default, and -1 indicates that the priority is not set
  • Focus Windows have the highest priority if they vote for the desired mode ID
  • Windows with focus but no mode specified are the default priority
  • No window, but the specified mode window, the lowest priority.
  • This is used for split screen scenarios. For example, if there are two Windows under the split screen, one with the focus not on the current system frame rate, and the other without the focus with the mode specified, in this case, we can set the mode to the window of the specified mode.

In addition, to accommodate more situations, WMS has added two class lists.

// The name of the non-high frame application package
private final ArraySet<String> mNonHighRefreshRatePackages = new ArraySet<>();
// The packet name in the blacklist is restricted from using high frames
private final HighRefreshRateBlacklist mHighRefreshRateBlacklist;
Copy the code
High frame blacklist

The HighRefreshRateBlacklist class manages the high-frame blacklist and provides update and judgment logic.

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

Read the blacklist in config_highRefreshRateBlacklist array.

Implementation logic in SurfaceFlinger

Layer.setFrameRateSelectionPriority && Layer.setFrameRate

The uppersetFrameRateSelectionPriorityThe corresponding is also of the bottom LayersetFrameRateSelectionPriority

The uppersetFrameRateThe corresponding is also of the bottom LayersetFrameRate

Layer.cpp

    struct State {. ./ / corresponding setFrameRateSelectionPriority set of parameters
        // Priority of the layer assigned by Window Manager.
        int32_t frameRateSelectionPriority;

        // The parameter corresponding to 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 {
        floatrate; FrameRateCompatibility type; . . };Copy the code

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.
    };
Copy the code

Mapping between upper FrameRateCompatibility and SurfaceFlinger FrameRateCompatibility:

  • 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);
            returnFrameRateCompatibility::Default; }}Copy the code

See the Android R code found frameRateSelectionPriority SF, does not use the parameters, Google has not finish this feature development.

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

Layer history statistics – for Heuristic calculations

When the Layer does not specify the required frame rate, it uses heuristic method to estimate the frame rate of the current Layer by calculating the historical data

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(a); it >= end) { std::iter_swap(it, end); mActiveLayersEnd++; }}Copy the code
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();
    }
}
Copy the code

Determine if layers are updated frequently

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; }Copy the code

Calculate the Layer refresh rate

std::optional<float> LayerInfoV2::calculateRefreshRateIfPossible(a) {
    static constexpr float MARGIN = 1.0 f; // 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(a); 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;
        }

        // Calculate the sum of the PresentTime Delta
        totalPresentTimeDeltas +=
                std::max(((it + 1)->presetTime - it->presetTime), mHighRefreshRatePeriod);
    }
    // Calculate the average time per frame
    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.
    // Remove burst frames to ensure that all captured frames are evenly distributed
    for (auto it = mFrameTimes.begin(a); it ! = mFrameTimes.end() - 1; ++it) {
        const nsecs_t presentTimeDeltas =
                std::max(((it + 1)->presetTime - it->presetTime), mHighRefreshRatePeriod);
        if (std::abs(presentTimeDeltas - averageFrameTime) > 2 * averageFrameTime) {
            returnstd::nullopt; }}const auto refreshRate = 1e9f / averageFrameTime;
    // If the difference is greater than 1HZ, update the frame rate
    if (std::abs(refreshRate - mLastReportedRefreshRate) > MARGIN) {
        mLastReportedRefreshRate = refreshRate;
    }

    ALOGV("Refresh rate: %.2f", mLastReportedRefreshRate);
    return mLastReportedRefreshRate;
}
Copy the code
std::pair<LayerHistory::LayerVoteType, float> LayerInfoV2::getRefreshRate(nsecs_t now) {
    // Only LayerVoteType == Heuristic calculates possible refresh rates
    if(mLayerVote.type ! = LayerHistory::LayerVoteType::Heuristic) {return {mLayerVote.type, mLayerVote.fps};
    }

    if (!isFrequent(now)) {
        return {LayerHistory::LayerVoteType::Min, 0};
    }

    auto refreshRate = calculateRefreshRateIfPossible(a);if (refreshRate.has_value()) {
        return {LayerHistory::LayerVoteType::Heuristic, refreshRate.value(a)}; }return {LayerHistory::LayerVoteType::Max, 0};
}
Copy the code

Screen refresh frame rate decision logic

In Android R, Windows are divided:

  • Wallpaper – Wallpaper layers run at a minimum frame rate
  • status bar – Doesn’t care about the refresh rate
  • Other Layers – Heuristic estimate frame 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); }}}Copy the code

Traverse Layer

  • If the layer is specified with the desired refresh rate (FRAME_RATE_COMPATIBILITY_FIXED_SOURCE),setLayerVotenoticeLayerInfoV2. inLayerInfoV2When calculating the frame rate, it is found that the specified frame rate is not heuristic, so it returns the specified frame rate
  • If it is,FRAME_RATE_COMPATIBILITY_DEFAULT) is the frame rate specified by the layer
  • If the layer abandons voting (NoVote), there is no requirement for refresh rate, and this layer should not be considered when determining display refresh rate (similar to statusbar/R corner/full screen gesture). So its frame rate depends on the other layers
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(a); layer &&isLayerActive(*layer, *info, threshold)) {
            i++;
            // Set layer vote if set
            const auto frameRate = layer->getFrameRateForLayerTree(a);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(a); }continue;
        }

        if (CC_UNLIKELY(mTraceEnabled)) {
            trace(weak, LayerHistory::LayerVoteType::NoVote, 0);
        }

        info->clearHistory(a); std::swap(mLayerInfos[i], mLayerInfos[--mActiveLayersEnd]);
    }

    // Collect expired layers after inactive layers.
    size_t end = mLayerInfos.size(a);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());
}

Copy the code

Calculate layer weights

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(a);if(! strong) {continue;
        }

        // TODO is not used
        // 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);
            // Layer abstention is not considered
            // Skip NoVote layer as those don't have any requirements
            if (type == LayerHistory::LayerVoteType::NoVote) {
                continue;
            }

            // Calculate the position of the layer in the screen
            // Compute the layer's position on the screen
            const Rect bounds = Rect(strong->getBounds());
            const ui::Transform transform = strong->getTransform(a);constexpr bool roundOutwards = true;
            Rect transformed = transform.transform(bounds, roundOutwards);

            // Calculate the area of the layer
            const float layerArea = transformed.getWidth() * transformed.getHeight(a);// Layer area/Display area = Layer proportion in display area = weight
            float weight = mDisplayArea ? layerArea / mDisplayArea : 0.0 f;
            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;
}
Copy the code

Determines the content refresh rate

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

// Select the frame rate according to the content
void Scheduler::chooseRefreshRateForContent(a) {
    if(! mLayerHistory)return;

    ATRACE_CALL(a); 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;// Frame rate calculation logic
        newConfigId = calculateRefreshRateConfigIndexType(a);if (mFeatures.configId == newConfigId) {
            return;
        }
        mFeatures.configId = newConfigId;
        auto& newRefreshRate = mRefreshRateConfigs.getRefreshRateFromConfigId(newConfigId);
        mSchedulerCallback.changeRefreshRate(newRefreshRate, ConfigEvent::Changed); }}Copy the code

CalculateRefreshRateConfigIndexType this function when calculating the display refresh rate, prioritization of various scenarios, from high to low as follows:

  • Power event
  • Touch events
  • Content refreshed (Idle)
HwcConfigIndexType Scheduler::calculateRefreshRateConfigIndexType(a) {
    ATRACE_CALL(a);// 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;
    // The most important call to this function
    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(a); }return ret;
}
Copy the code

IsDisplayPowerStateNormal corresponding is HWC_POWER_MODE_NORMAL

GetRefreshRateForContentV2 is exclude higher priority after the scene, calculate the display refresh rate function.

Where the mAvailableRefreshRates variable is sorted.

const RefreshRate& RefreshRateConfigs::getRefreshRateForContentV2(
        const std::vector<LayerRequirement>& layers, bool touchActive,
        bool* touchConsidered) const {
    ATRACE_CALL(a);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 there is no Layer and no content check,
    if (layers.empty()) {
        *touchConsidered = touchActive;
        return touchActive ? *mAvailableRefreshRates.back() : getCurrentRefreshRateByPolicyLocked(a); }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.
    // Corresponds to the upper FRAME_RATE_COMPATIBILITY_DEFAULT
    // Layer specifies the frame rate
    // The Android native policy for such layers is that if such layers exist, getting the touch event will not increase the frame rate to the maximum
    if (touchActive && explicitDefaultVoteLayers == 0) {
        *touchConsidered = true;
        / / mAvailableRefreshRates. Back () the biggest frame rate
        return *mAvailableRefreshRates.back(a); }// Only if all layers want Min we should return Min
    if (noVoteLayers + minVoteLayers == layers.size()) {
        / / mAvailableRefreshRates. Front () the minimum frame rate
        return *mAvailableRefreshRates.front(a); }// Find the best refresh rate based on score
    std::vector<std::pair<const RefreshRate*, float>> scores;
    // Assign scores vector the same size as mAvailableRefreshRates
    scores.reserve(mAvailableRefreshRates.size());

    for (const auto refreshRate : mAvailableRefreshRates) {
        // Set scores to 0.0f
        scores.emplace_back(refreshRate, 0.0 f);
    }

    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(a); i++) {// If Layer wants the maximum frame rate, assign the highest score to the highest frame rate
            // If the layer wants Max, give higher score to the higher refresh rate
            if (layer.vote == LayerVoteType::Max) {
                // scores.back().first-> FPS is the maximum frame rate
                // The current frame rate is divided by the maximum frame rate
                // The maximum frame rate is divided by the maximum frame rate, resulting in a value = 1
                // Other frame rates that are less than the maximum frame rate score much less than the maximum frame rate when squared
                // In this way, the highest score is assigned to the highest frame rate
                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);
                // The frame rate for this Layer score times the weight
                scores[i].second += weight * layerScore;
                continue;
            }

            // Note that the interval, not the framerate, is inversely proportional to the framerate size !!!!
            const auto displayPeriod = scores[i].first->vsyncPeriod;
            // desiredRefreshRate is in the LayerRequirement structure
            / / mentioned above, using the Summary = STD: : vector < RefreshRateConfigs: : LayerRequirement >;
            // The refreshRate in Summary comes from LayerInfoV2's getRefreshRate() function
            // This function returns {mlayerVote. type, mlayerVote. FPS};
            // FPS in mLayerVote is set in the following way
            // info->setLayerVote(voteType, frameRate.rate);
            // The rate in FrameRate is passed down from the setFrameRate interface
            // 所以 layer.desiredRefreshRate == frameRate.rate
            const auto layerPeriod = round<nsecs_t> (1e9f / layer.desiredRefreshRate);
            // Corresponds to the upper 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
                    // If Layer requires a period larger than the current period, keep the current refresh period increasing
                    auto actualLayerPeriod = displayPeriod;
                    int multiplier = 1;
                    while (layerPeriod > actualLayerPeriod + MARGIN_FOR_PERIOD_CALCULATION) {
                        multiplier++;
                        actualLayerPeriod = displayPeriod * multiplier;
                    }
                    // layerPeriod/actualLayerPeriod this value has two possibilities
                    // 1. actualLayerPeriod < layerPeriod
                    // && actualLayerPeriod + MARGIN_FOR_PERIOD_CALCULATION > layerPeriod
                    // Then layerPeriod/actualLayerPeriod > 1, the frame rate for this Layer score = 1
                    // 2. actualLayerPeriod > layerPeriod
                    // So layerPeriod/actualLayerPeriod < 1, the frame rate for this Layer score < 1
                    // You can see that the first situation is more favorable. This is because if the layer refresh frequency is greater than the display frequency,
                    // If the layer refresh rate is less than the display frame rate and the two are close, each frame will be displayed even without many duplicate frames
                    return std::min(1.0 f.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 specifies the refresh rate or the case of the Layer content estimated by heuristics
            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 is in the layerPeriod/displayPeriod range
                    // Quotient and remainder, which contains the case for rounding (800us)
                    const auto [displayFramesQuot, displayFramesRem] =
                            getDisplayFrames(layerPeriod, displayPeriod);
                    static constexpr size_t MAX_FRAMES_TO_FIT =
                            10; // Stop when score < 0.1
                    // If remainder == 0, the refresh rate required by the layer is an integer multiple of the display refresh rate
                    // This is considered a match, and the framerate scores = 1 for this Layer
                    if (displayFramesRem == 0) {
                        // Layer desired refresh rate matches the display rate.
                        return 1.0 f;
                    }

                    // If the factor == 0, it means that the frame rate required by the layer is much greater than the currently provided display frame rate
                    // The greater the difference, the lower the score
                    if (displayFramesQuot == 0) {
                        // Layer desired refresh rate is higher the display rate.
                        return (static_cast<float>(layerPeriod) /
                                static_cast<float>(displayPeriod)) *
                                (1.0 f / (MAX_FRAMES_TO_FIT + 1));
                    }

                    LayerPeriod > displayPeriod
                    // layerPeriod/displayPeriod is not divisible
                    // Layer requires a much lower framerate than the display framerate
                    // 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++;
                    }
					// The smaller the difference, the higher the score
                    return 1.0 f/ 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;
}
Copy the code

GetBestRefreshRate gets the highest score for the most suitable refresh rate

const RefreshRate* RefreshRateConfigs::getBestRefreshRate(Iter begin, Iter end) const {
    constexpr auto EPSILON = 0.001 f;
    const RefreshRate* bestRefreshRate = begin->first;
    float max = begin->second;
    for (autoi = 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;
}

Copy the code

Conclusion:

To summarize the above logic:

  • Interfaces that allow Applications to vote on refresh frame rates based on their own scenarios are exposed in the SDK & NDK
  • WMS divides frame rate priority for focus, split screen, etc., and provides a blacklist mechanism
  • Based on this information, SurfaceFlinger makes the final decision on the frame rate of the display
    • If Applications or the Framework does not set refresh rate and the content refresh rate statistics logic is turned on in the configuration, then the layer content refresh rate is heuristically estimated based on the layer refresh history and the voting type is updated (LayerVoteType)
    • If Applications or the Framework sets the refresh rate, use the one set by the APP and update the voting type (LayerVoteType)
    • Iterate over each Layer, calculate the proportion of Layer in the display area, used as a weight
    • Consider special scenarios, such as Power and Touch, which have higher priorities
    • When there is no special scenario, depending on the differenceLayerVoteTypeThe number of layers determines the refresh rate first
    • If none of the above decisions are successful, all layers are traversed and all supported display frame rates are traversed. According to Layer’s requirements, a score is given for each display frame rate
    • The frame rate that gets the most points is the frame rate that is most likely to satisfy all scenarios

Reference:

Developer.android.com/guide/topic…

Android R Variable Refresh Rate research