Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1adbb727 authored by Ady Abraham's avatar Ady Abraham
Browse files

SurfaceFlinger: more aggressive infrequent layer detection

Change the algorithm that chooses the refresh rate to treat layers as
infrequent unless proven otherwise. This change helps with multiple
switches during scenarios of blinking cursor where the detection of
infrequent layer is too long. The down side on this change is that
animations will be considered infrequent as well for the first few frames.
However the touch boost is a good mitigation of this.

Test: Typing in Messages and observe refresh rate
Test: Settings->About->up time and observe refresh rate
Bug: 155062712
Bug: 156654519
Change-Id: I317c69bd063df5d70f2d5705163cf61c1c9b1fff
parent b209e7b5
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -90,7 +90,7 @@ LayerHistoryV2::~LayerHistoryV2() = default;
void LayerHistoryV2::registerLayer(Layer* layer, float /*lowRefreshRate*/, float highRefreshRate,
                                   LayerVoteType type) {
    const nsecs_t highRefreshRatePeriod = static_cast<nsecs_t>(1e9f / highRefreshRate);
    auto info = std::make_unique<LayerInfoV2>(highRefreshRatePeriod, type);
    auto info = std::make_unique<LayerInfoV2>(layer->getName(), highRefreshRatePeriod, type);
    std::lock_guard lock(mLock);
    mLayerInfos.emplace_back(layer, std::move(info));
}
+19 −36
Original line number Diff line number Diff line
@@ -27,8 +27,10 @@

namespace android::scheduler {

LayerInfoV2::LayerInfoV2(nsecs_t highRefreshRatePeriod, LayerHistory::LayerVoteType defaultVote)
      : mHighRefreshRatePeriod(highRefreshRatePeriod),
LayerInfoV2::LayerInfoV2(const std::string& name, nsecs_t highRefreshRatePeriod,
                         LayerHistory::LayerVoteType defaultVote)
      : mName(name),
        mHighRefreshRatePeriod(highRefreshRatePeriod),
        mDefaultVote(defaultVote),
        mLayerVote({defaultVote, 0.0f}) {}

@@ -45,54 +47,31 @@ void LayerInfoV2::setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now) {
    }
}

bool LayerInfoV2::isFrameTimeValid(const FrameTimeData& frameTime) const {
    return frameTime.queueTime >= std::chrono::duration_cast<std::chrono::nanoseconds>(
                                          mFrameTimeValidSince.time_since_epoch())
                                          .count();
}

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;
        }
    for (auto it = mFrameTimes.crbegin(); it != mFrameTimes.crend(); ++it) {
        if (now - it->queueTime >= MAX_FREQUENT_LAYER_PERIOD_NS.count()) {
            ALOGV("%s infrequent (last frame is %.2fms ago", mName.c_str(),
                  (now - mFrameTimes.back().queueTime) / 1e6f);
            return false;
        }

    // 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) {
        const auto numFrames = std::distance(mFrameTimes.crbegin(), it + 1);
        if (numFrames >= FREQUENT_LAYER_WINDOW_SIZE) {
            ALOGV("%s frequent (burst of %zu frames", mName.c_str(), numFrames);
            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) {
    ALOGV("%s infrequent (not enough frames %zu)", mName.c_str(), mFrameTimes.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;
}

bool LayerInfoV2::hasEnoughDataForHeuristic() const {
    // The layer had to publish at least HISTORY_SIZE or HISTORY_TIME of updates
    if (mFrameTimes.size() < 2) {
        return false;
    }

    if (!isFrameTimeValid(mFrameTimes.front())) {
        return false;
    }

    if (mFrameTimes.size() < HISTORY_SIZE &&
        mFrameTimes.back().queueTime - mFrameTimes.front().queueTime < HISTORY_TIME.count()) {
        return false;
@@ -167,18 +146,22 @@ std::optional<float> LayerInfoV2::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 (!isFrequent(now)) {
        ALOGV("%s is infrequent", mName.c_str());
        return {LayerHistory::LayerVoteType::Min, 0};
    }

    auto refreshRate = calculateRefreshRateIfPossible();
    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};
}

+5 −9
Original line number Diff line number Diff line
@@ -54,7 +54,8 @@ class LayerInfoV2 {
    friend class LayerHistoryTestV2;

public:
    LayerInfoV2(nsecs_t highRefreshRatePeriod, LayerHistory::LayerVoteType defaultVote);
    LayerInfoV2(const std::string& name, nsecs_t highRefreshRatePeriod,
                LayerHistory::LayerVoteType defaultVote);

    LayerInfoV2(const LayerInfo&) = delete;
    LayerInfoV2& operator=(const LayerInfoV2&) = delete;
@@ -83,11 +84,7 @@ public:
    nsecs_t getLastUpdatedTime() const { return mLastUpdatedTime; }

    void clearHistory() {
        // Mark mFrameTimeValidSince to now to ignore all previous frame times.
        // We are not deleting the old frame to keep track of whether we should treat the first
        // buffer as Max as we don't know anything about this layer or Min as this layer is
        // posting infrequent updates.
        mFrameTimeValidSince = std::chrono::steady_clock::now();
        mFrameTimes.clear();
        mLastReportedRefreshRate = 0.0f;
    }

@@ -101,7 +98,8 @@ private:
    bool isFrequent(nsecs_t now) const;
    bool hasEnoughDataForHeuristic() const;
    std::optional<float> calculateRefreshRateIfPossible();
    bool isFrameTimeValid(const FrameTimeData&) const;

    const std::string mName;

    // Used for sanitizing the heuristic data
    const nsecs_t mHighRefreshRatePeriod;
@@ -118,8 +116,6 @@ private:
    } mLayerVote;

    std::deque<FrameTimeData> mFrameTimes;
    std::chrono::time_point<std::chrono::steady_clock> mFrameTimeValidSince =
            std::chrono::steady_clock::now();
    static constexpr size_t HISTORY_SIZE = 90;
    static constexpr std::chrono::nanoseconds HISTORY_TIME = 1s;
};
+11 −6
Original line number Diff line number Diff line
@@ -103,7 +103,7 @@ const RefreshRate& RefreshRateConfigs::getBestRefreshRate(
    ATRACE_CALL();
    ALOGV("getRefreshRateForContent %zu layers", layers.size());

    *touchConsidered = false;
    if (touchConsidered) *touchConsidered = false;
    std::lock_guard lock(mLock);

    int noVoteLayers = 0;
@@ -131,7 +131,8 @@ const RefreshRate& RefreshRateConfigs::getBestRefreshRate(
    // 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 (touchActive && explicitDefaultVoteLayers == 0 && explicitExactOrMultipleVoteLayers == 0) {
        *touchConsidered = true;
        ALOGV("TouchBoost - choose %s", getMaxRefreshRateByPolicyLocked().getName().c_str());
        if (touchConsidered) *touchConsidered = true;
        return getMaxRefreshRateByPolicyLocked();
    }

@@ -145,6 +146,7 @@ const RefreshRate& RefreshRateConfigs::getBestRefreshRate(

    // Only if all layers want Min we should return Min
    if (noVoteLayers + minVoteLayers == layers.size()) {
        ALOGV("all layers Min - choose %s", getMinRefreshRateByPolicyLocked().getName().c_str());
        return getMinRefreshRateByPolicyLocked();
    }

@@ -243,9 +245,11 @@ const RefreshRate& RefreshRateConfigs::getBestRefreshRate(

                    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);
                ALOGV("%s (%s, weight %.2f) %.2fHz gives %s score of %.2f", layer.name.c_str(),
                      layer.vote == LayerVoteType::ExplicitExactOrMultiple
                              ? "ExplicitExactOrMultiple"
                              : "Heuristic",
                      weight, 1e9f / layerPeriod, scores[i].first->name.c_str(), layerScore);
                scores[i].second += weight * layerScore;
                continue;
            }
@@ -266,7 +270,8 @@ const RefreshRate& RefreshRateConfigs::getBestRefreshRate(
    const RefreshRate& touchRefreshRate = getMaxRefreshRateByPolicyLocked();
    if (touchActive && explicitDefaultVoteLayers == 0 &&
        bestRefreshRate->fps < touchRefreshRate.fps) {
        *touchConsidered = true;
        if (touchConsidered) *touchConsidered = true;
        ALOGV("TouchBoost - choose %s", touchRefreshRate.getName().c_str());
        return touchRefreshRate;
    }

+14 −18
Original line number Diff line number Diff line
@@ -526,7 +526,9 @@ void Scheduler::idleTimerCallback(TimerState state) {

void Scheduler::touchTimerCallback(TimerState state) {
    const TouchState touch = state == TimerState::Reset ? TouchState::Active : TouchState::Inactive;
    handleTimerStateChanged(&mFeatures.touch, touch, true /* eventOnContentDetection */);
    if (handleTimerStateChanged(&mFeatures.touch, touch, true /* eventOnContentDetection */)) {
        mLayerHistory->clear();
    }
    ATRACE_INT("TouchState", static_cast<int>(touch));
}

@@ -549,18 +551,19 @@ void Scheduler::dump(std::string& result) const {
}

template <class T>
void Scheduler::handleTimerStateChanged(T* currentState, T newState, bool eventOnContentDetection) {
bool Scheduler::handleTimerStateChanged(T* currentState, T newState, bool eventOnContentDetection) {
    ConfigEvent event = ConfigEvent::None;
    HwcConfigIndexType newConfigId;
    bool touchConsidered = false;
    {
        std::lock_guard<std::mutex> lock(mFeatureStateLock);
        if (*currentState == newState) {
            return;
            return touchConsidered;
        }
        *currentState = newState;
        newConfigId = calculateRefreshRateConfigIndexType();
        newConfigId = calculateRefreshRateConfigIndexType(&touchConsidered);
        if (mFeatures.configId == newConfigId) {
            return;
            return touchConsidered;
        }
        mFeatures.configId = newConfigId;
        if (eventOnContentDetection && !mFeatures.contentRequirements.empty()) {
@@ -569,10 +572,12 @@ void Scheduler::handleTimerStateChanged(T* currentState, T newState, bool eventO
    }
    const RefreshRate& newRefreshRate = mRefreshRateConfigs.getRefreshRateFromConfigId(newConfigId);
    mSchedulerCallback.changeRefreshRate(newRefreshRate, event);
    return touchConsidered;
}

HwcConfigIndexType Scheduler::calculateRefreshRateConfigIndexType() {
HwcConfigIndexType Scheduler::calculateRefreshRateConfigIndexType(bool* touchConsidered) {
    ATRACE_CALL();
    if (touchConsidered) *touchConsidered = false;

    // 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.
@@ -607,18 +612,9 @@ HwcConfigIndexType Scheduler::calculateRefreshRateConfigIndexType() {
                .getConfigId();
    }

    bool touchConsidered;
    const auto& ret = mRefreshRateConfigs
                              .getBestRefreshRate(mFeatures.contentRequirements, touchActive, idle,
                                                  &touchConsidered)
    return mRefreshRateConfigs
            .getBestRefreshRate(mFeatures.contentRequirements, touchActive, idle, touchConsidered)
            .getConfigId();
    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;
}

std::optional<HwcConfigIndexType> Scheduler::getPreferredConfigId() {
Loading