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

Commit 0ccd79b1 authored by Ady Abraham's avatar Ady Abraham
Browse files

SurfaceFlinger: stabilize heuristic refresh rate calculation

When calculating layer's refresh rate heuristically, also store
a history of the last refresh rate calculated. We use this list to
determine how consistent is the refresh rate. If the refresh rate is
not consistent, then we keep reporting the last consistent refresh rate
and wait for the refresh rate to stabilize again.

Test: Play at 60fps video in XPlayer
Bug: 157540021

Change-Id: If3b525820d298cc5835dddf73f327501c8a18964
parent b1b9d41c
Loading
Loading
Loading
Loading
+14 −21
Original line number Diff line number Diff line
@@ -58,30 +58,21 @@ bool useFrameRatePriority() {
    return atoi(value);
}

void trace(const wp<Layer>& weak, LayerHistory::LayerVoteType type, int fps) {
void trace(const wp<Layer>& weak, const LayerInfoV2& info, LayerHistory::LayerVoteType type,
           int fps) {
    const auto layer = weak.promote();
    if (!layer) return;

    const auto makeTag = [layer](LayerHistory::LayerVoteType vote) {
        return "LFPS " + RefreshRateConfigs::layerVoteTypeString(vote) + " " + layer->getName();
    const auto traceType = [&](LayerHistory::LayerVoteType checkedType, int value) {
        ATRACE_INT(info.getTraceTag(checkedType), type == checkedType ? value : 0);
    };

    const auto noVoteTag = makeTag(LayerHistory::LayerVoteType::NoVote);
    const auto heuristicVoteTag = makeTag(LayerHistory::LayerVoteType::Heuristic);
    const auto explicitDefaultVoteTag = makeTag(LayerHistory::LayerVoteType::ExplicitDefault);
    const auto explicitExactOrMultipleVoteTag =
            makeTag(LayerHistory::LayerVoteType::ExplicitExactOrMultiple);
    const auto minVoteTag = makeTag(LayerHistory::LayerVoteType::Min);
    const auto maxVoteTag = makeTag(LayerHistory::LayerVoteType::Max);

    ATRACE_INT(noVoteTag.c_str(), type == LayerHistory::LayerVoteType::NoVote ? 1 : 0);
    ATRACE_INT(heuristicVoteTag.c_str(), type == LayerHistory::LayerVoteType::Heuristic ? fps : 0);
    ATRACE_INT(explicitDefaultVoteTag.c_str(),
               type == LayerHistory::LayerVoteType::ExplicitDefault ? fps : 0);
    ATRACE_INT(explicitExactOrMultipleVoteTag.c_str(),
               type == LayerHistory::LayerVoteType::ExplicitExactOrMultiple ? fps : 0);
    ATRACE_INT(minVoteTag.c_str(), type == LayerHistory::LayerVoteType::Min ? 1 : 0);
    ATRACE_INT(maxVoteTag.c_str(), type == LayerHistory::LayerVoteType::Max ? 1 : 0);
    traceType(LayerHistory::LayerVoteType::NoVote, 1);
    traceType(LayerHistory::LayerVoteType::Heuristic, fps);
    traceType(LayerHistory::LayerVoteType::ExplicitDefault, fps);
    traceType(LayerHistory::LayerVoteType::ExplicitExactOrMultiple, fps);
    traceType(LayerHistory::LayerVoteType::Min, 1);
    traceType(LayerHistory::LayerVoteType::Max, 1);

    ALOGD("%s: %s @ %d Hz", __FUNCTION__, layer->getName().c_str(), fps);
}
@@ -89,6 +80,7 @@ void trace(const wp<Layer>& weak, LayerHistory::LayerVoteType type, int fps) {

LayerHistoryV2::LayerHistoryV2(const scheduler::RefreshRateConfigs& refreshRateConfigs)
      : mTraceEnabled(traceEnabled()), mUseFrameRatePriority(useFrameRatePriority()) {
    LayerInfoV2::setTraceEnabled(mTraceEnabled);
    LayerInfoV2::setRefreshRateConfigs(refreshRateConfigs);
}

@@ -154,7 +146,7 @@ LayerHistoryV2::Summary LayerHistoryV2::summarize(nsecs_t now) {
        summary.push_back({strong->getName(), type, refreshRate, weight});

        if (CC_UNLIKELY(mTraceEnabled)) {
            trace(layer, type, static_cast<int>(std::round(refreshRate)));
            trace(layer, *info, type, static_cast<int>(std::round(refreshRate)));
        }
    }

@@ -193,7 +185,7 @@ void LayerHistoryV2::partitionLayers(nsecs_t now) {
        }

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

        info->onLayerInactive(now);
@@ -220,4 +212,5 @@ void LayerHistoryV2::clear() {
        info->clearHistory(systemTime());
    }
}

} // namespace android::scheduler::impl
+3 −3
Original line number Diff line number Diff line
@@ -98,9 +98,9 @@ class LayerInfo {
                return false;
            }

            // The layer had to publish at least HISTORY_SIZE or HISTORY_TIME of updates
            // The layer had to publish at least HISTORY_SIZE or HISTORY_DURATION of updates
            if (mElements.size() < HISTORY_SIZE &&
                mElements.back() - mElements.front() < HISTORY_TIME.count()) {
                mElements.back() - mElements.front() < HISTORY_DURATION.count()) {
                return false;
            }

@@ -124,7 +124,7 @@ class LayerInfo {

    private:
        std::deque<nsecs_t> mElements;
        static constexpr std::chrono::nanoseconds HISTORY_TIME = 1s;
        static constexpr std::chrono::nanoseconds HISTORY_DURATION = 1s;
    };

    friend class LayerHistoryTest;
+119 −50
Original line number Diff line number Diff line
@@ -15,26 +15,31 @@
 */

// #define LOG_NDEBUG 0
#define ATRACE_TAG ATRACE_TAG_GRAPHICS

#include "LayerInfoV2.h"

#include <algorithm>
#include <utility>

#include <cutils/compiler.h>
#include <cutils/trace.h>

#undef LOG_TAG
#define LOG_TAG "LayerInfoV2"
#define ATRACE_TAG ATRACE_TAG_GRAPHICS

namespace android::scheduler {

const RefreshRateConfigs* LayerInfoV2::sRefreshRateConfigs = nullptr;
bool LayerInfoV2::sTraceEnabled = false;

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

void LayerInfoV2::setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now,
                                     LayerUpdateType updateType, bool pendingConfigChange) {
@@ -94,24 +99,28 @@ bool LayerInfoV2::isAnimating(nsecs_t now) const {
}

bool LayerInfoV2::hasEnoughDataForHeuristic() const {
    // The layer had to publish at least HISTORY_SIZE or HISTORY_TIME of updates
    // The layer had to publish at least HISTORY_SIZE or HISTORY_DURATION of updates
    if (mFrameTimes.size() < 2) {
        ALOGV("fewer than 2 frames recorded: %zu", mFrameTimes.size());
        return false;
    }

    if (!isFrameTimeValid(mFrameTimes.front())) {
        ALOGV("stale frames still captured");
        return false;
    }

    if (mFrameTimes.size() < HISTORY_SIZE &&
        mFrameTimes.back().queueTime - mFrameTimes.front().queueTime < HISTORY_TIME.count()) {
    const auto totalDuration = mFrameTimes.back().queueTime - mFrameTimes.front().queueTime;
    if (mFrameTimes.size() < HISTORY_SIZE && totalDuration < HISTORY_DURATION.count()) {
        ALOGV("not enough frames captured: %zu | %.2f seconds", mFrameTimes.size(),
              totalDuration / 1e9f);
        return false;
    }

    return true;
}

std::pair<nsecs_t, bool> LayerInfoV2::calculateAverageFrameTime() const {
std::optional<nsecs_t> LayerInfoV2::calculateAverageFrameTime() const {
    nsecs_t totalPresentTimeDeltas = 0;
    nsecs_t totalQueueTimeDeltas = 0;
    bool missingPresentTime = false;
@@ -119,15 +128,20 @@ std::pair<nsecs_t, bool> LayerInfoV2::calculateAverageFrameTime() const {
    for (auto it = mFrameTimes.begin(); it != mFrameTimes.end() - 1; ++it) {
        // Ignore frames captured during a config change
        if (it->pendingConfigChange || (it + 1)->pendingConfigChange) {
            continue;
            return std::nullopt;
        }

        totalQueueTimeDeltas +=
                std::max(((it + 1)->queueTime - it->queueTime), mHighRefreshRatePeriod);
        numFrames++;

        if (it->presetTime == 0 || (it + 1)->presetTime == 0) {
        if (!missingPresentTime && (it->presetTime == 0 || (it + 1)->presetTime == 0)) {
            missingPresentTime = true;
            // If there are no presentation timestamps and we haven't calculated
            // one in the past then we can't calculate the refresh rate
            if (mLastRefreshRate.reported == 0) {
                return std::nullopt;
            }
            continue;
        }

@@ -142,61 +156,46 @@ std::pair<nsecs_t, bool> LayerInfoV2::calculateAverageFrameTime() const {
    // when implementing render ahead for specific refresh rates. When hwui no longer provides
    // presentation timestamps we look at the queue time to see if the current refresh rate still
    // matches the content.

    const auto averageFrameTime =
            static_cast<float>(missingPresentTime ? totalQueueTimeDeltas : totalPresentTimeDeltas) /
            numFrames;
    return {static_cast<nsecs_t>(averageFrameTime), missingPresentTime};
}

bool LayerInfoV2::isRefreshRateStable(nsecs_t averageFrameTime, bool missingPresentTime) const {
    for (auto it = mFrameTimes.begin(); it != mFrameTimes.end() - 1; ++it) {
        // Ignore frames captured during a config change
        if (it->pendingConfigChange || (it + 1)->pendingConfigChange) {
            continue;
    return static_cast<nsecs_t>(averageFrameTime);
}
        const auto presentTimeDeltas = [&] {
            const auto delta = missingPresentTime ? (it + 1)->queueTime - it->queueTime
                                                  : (it + 1)->presetTime - it->presetTime;
            return std::max(delta, mHighRefreshRatePeriod);
        }();

        if (std::abs(presentTimeDeltas - averageFrameTime) > 2 * averageFrameTime) {
            return false;
        }
    }

    return true;
}

std::optional<float> LayerInfoV2::calculateRefreshRateIfPossible() {
std::optional<float> LayerInfoV2::calculateRefreshRateIfPossible(nsecs_t now) {
    static constexpr float MARGIN = 1.0f; // 1Hz
    if (!hasEnoughDataForHeuristic()) {
        ALOGV("Not enough data");
        return std::nullopt;
    }

    const auto [averageFrameTime, missingPresentTime] = calculateAverageFrameTime();
    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);

    // If there are no presentation timestamps provided we can't calculate the refresh rate
    if (missingPresentTime && mLastRefreshRate.reported == 0) {
        return std::nullopt;
    }

    if (!isRefreshRateStable(averageFrameTime, missingPresentTime)) {
        return std::nullopt;
    }

    const auto refreshRate = 1e9f / averageFrameTime;
    const auto knownRefreshRate = sRefreshRateConfigs->findClosestKnownFrameRate(refreshRate);
            // To avoid oscillation, use the last calculated refresh rate if it is
            // close enough
            if (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);
    return mLastRefreshRate.reported;
            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);
}

std::pair<LayerHistory::LayerVoteType, float> LayerInfoV2::getRefreshRate(nsecs_t now) {
@@ -207,15 +206,24 @@ std::pair<LayerHistory::LayerVoteType, float> LayerInfoV2::getRefreshRate(nsecs_

    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};
    }

    auto refreshRate = calculateRefreshRateIfPossible();
    // 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 data
    if (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()};
@@ -225,4 +233,65 @@ std::pair<LayerHistory::LayerVoteType, float> LayerInfoV2::getRefreshRate(nsecs_
    return {LayerHistory::LayerVoteType::Max, 0};
}

const char* LayerInfoV2::getTraceTag(android::scheduler::LayerHistory::LayerVoteType type) const {
    if (mTraceTags.count(type) == 0) {
        const auto tag = "LFPS " + mName + " " + RefreshRateConfigs::layerVoteTypeString(type);
        mTraceTags.emplace(type, tag);
    }

    return mTraceTags.at(type).c_str();
}

LayerInfoV2::RefreshRateHistory::HeuristicTraceTagData
LayerInfoV2::RefreshRateHistory::makeHeuristicTraceTagData() const {
    const std::string prefix = "LFPS ";
    const std::string suffix = "Heuristic ";
    return {.min = prefix + mName + suffix + "min",
            .max = prefix + mName + suffix + "max",
            .consistent = prefix + mName + suffix + "consistent",
            .average = prefix + mName + suffix + "average"};
}

void LayerInfoV2::RefreshRateHistory::clear() {
    mRefreshRates.clear();
}

bool LayerInfoV2::RefreshRateHistory::add(float refreshRate, nsecs_t now) {
    mRefreshRates.push_back({refreshRate, now});
    while (mRefreshRates.size() >= HISTORY_SIZE ||
           now - mRefreshRates.front().timestamp > HISTORY_DURATION.count()) {
        mRefreshRates.pop_front();
    }

    if (CC_UNLIKELY(sTraceEnabled)) {
        if (!mHeuristicTraceTagData.has_value()) {
            mHeuristicTraceTagData = makeHeuristicTraceTagData();
        }

        ATRACE_INT(mHeuristicTraceTagData->average.c_str(), static_cast<int>(refreshRate));
    }

    return isConsistent();
}

bool LayerInfoV2::RefreshRateHistory::isConsistent() const {
    if (mRefreshRates.empty()) return true;

    const auto max = std::max_element(mRefreshRates.begin(), mRefreshRates.end());
    const auto min = std::min_element(mRefreshRates.begin(), mRefreshRates.end());
    const auto consistent = max->refreshRate - min->refreshRate <= MARGIN_FPS;

    if (CC_UNLIKELY(sTraceEnabled)) {
        if (!mHeuristicTraceTagData.has_value()) {
            mHeuristicTraceTagData = makeHeuristicTraceTagData();
        }

        ATRACE_INT(mHeuristicTraceTagData->max.c_str(), static_cast<int>(max->refreshRate));
        ATRACE_INT(mHeuristicTraceTagData->min.c_str(), static_cast<int>(min->refreshRate));
        ATRACE_INT(mHeuristicTraceTagData->consistent.c_str(), consistent);
    }

    return consistent;
}

} // namespace android::scheduler
+79 −17
Original line number Diff line number Diff line
@@ -56,6 +56,8 @@ class LayerInfoV2 {
    friend class LayerHistoryTestV2;

public:
    static void setTraceEnabled(bool enabled) { sTraceEnabled = enabled; }

    static void setRefreshRateConfigs(const RefreshRateConfigs& refreshRateConfigs) {
        sRefreshRateConfigs = &refreshRateConfigs;
    }
@@ -90,6 +92,9 @@ public:
    // updated time, the updated time is the present time.
    nsecs_t getLastUpdatedTime() const { return mLastUpdatedTime; }

    // Returns a C string for tracing a vote
    const char* getTraceTag(LayerHistory::LayerVoteType type) const;

    void onLayerInactive(nsecs_t now) {
        // 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
@@ -98,6 +103,7 @@ public:
        const auto timePoint = std::chrono::nanoseconds(now);
        mFrameTimeValidSince = std::chrono::time_point<std::chrono::steady_clock>(timePoint);
        mLastRefreshRate = {};
        mRefreshRateHistory.clear();
    }

    void clearHistory(nsecs_t now) {
@@ -113,12 +119,73 @@ private:
        bool pendingConfigChange;
    };

    // Holds information about the calculated and reported refresh rate
    struct RefreshRateHeuristicData {
        // Rate calculated on the layer
        float calculated = 0.0f;
        // Last reported rate for LayerInfoV2::getRefreshRate()
        float reported = 0.0f;
        // Whether the last reported rate for LayerInfoV2::getRefreshRate()
        // was due to animation or infrequent updates
        bool animatingOrInfrequent = false;
    };

    // Holds information about the layer vote
    struct LayerVote {
        LayerHistory::LayerVoteType type = LayerHistory::LayerVoteType::Heuristic;
        float fps = 0.0f;
    };

    // Class to store past calculated refresh rate and determine whether
    // the refresh rate calculated is consistent with past values
    class RefreshRateHistory {
    public:
        static constexpr auto HISTORY_SIZE = 90;
        static constexpr std::chrono::nanoseconds HISTORY_DURATION = 2s;

        RefreshRateHistory(const std::string& name) : mName(name) {}

        // Clears History
        void clear();

        // Adds a new refresh rate and returns true if it is consistent
        bool add(float refreshRate, nsecs_t now);

    private:
        friend class LayerHistoryTestV2;

        // Holds the refresh rate when it was calculated
        struct RefreshRateData {
            float refreshRate = 0.0f;
            nsecs_t timestamp = 0;

            bool operator<(const RefreshRateData& other) const {
                return refreshRate < other.refreshRate;
            }
        };

        // Holds tracing strings
        struct HeuristicTraceTagData {
            std::string min;
            std::string max;
            std::string consistent;
            std::string average;
        };

        bool isConsistent() const;
        HeuristicTraceTagData makeHeuristicTraceTagData() const;

        const std::string mName;
        mutable std::optional<HeuristicTraceTagData> mHeuristicTraceTagData;
        std::deque<RefreshRateData> mRefreshRates;
        static constexpr float MARGIN_FPS = 1.0;
    };

    bool isFrequent(nsecs_t now) const;
    bool isAnimating(nsecs_t now) const;
    bool hasEnoughDataForHeuristic() const;
    std::optional<float> calculateRefreshRateIfPossible();
    std::pair<nsecs_t, bool> calculateAverageFrameTime() const;
    bool isRefreshRateStable(nsecs_t averageFrameTime, bool missingPresentTime) const;
    std::optional<float> calculateRefreshRateIfPossible(nsecs_t now);
    std::optional<nsecs_t> calculateAverageFrameTime() const;
    bool isFrameTimeValid(const FrameTimeData&) const;

    const std::string mName;
@@ -127,32 +194,27 @@ private:
    const nsecs_t mHighRefreshRatePeriod;
    LayerHistory::LayerVoteType mDefaultVote;

    LayerVote mLayerVote;

    nsecs_t mLastUpdatedTime = 0;

    nsecs_t mLastAnimationTime = 0;

    // Holds information about the calculated and reported refresh rate
    struct RefreshRateHeuristicData {
        float calculated = 0.0f; // Rate calculated on the layer
        float reported = 0.0f;   // Last reported rate for LayerInfoV2::getRefreshRate()
    };

    RefreshRateHeuristicData mLastRefreshRate;

    // Holds information about the layer vote
    struct {
        LayerHistory::LayerVoteType type;
        float fps;
    } 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;
    static constexpr size_t HISTORY_SIZE = RefreshRateHistory::HISTORY_SIZE;
    static constexpr std::chrono::nanoseconds HISTORY_DURATION = 1s;

    RefreshRateHistory mRefreshRateHistory;

    mutable std::unordered_map<LayerHistory::LayerVoteType, std::string> mTraceTags;

    // Shared for all LayerInfo instances
    static const RefreshRateConfigs* sRefreshRateConfigs;
    static bool sTraceEnabled;
};

} // namespace scheduler
+82 −49

File changed.

Preview size limit exceeded, changes collapsed.