• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 // TODO(b/129481165): remove the #pragma below and fix conversion issues
18 #pragma clang diagnostic push
19 #pragma clang diagnostic ignored "-Wextra"
20 
21 // #define LOG_NDEBUG 0
22 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
23 
24 #include "LayerInfo.h"
25 
26 #include <algorithm>
27 #include <utility>
28 
29 #include <cutils/compiler.h>
30 #include <cutils/trace.h>
31 
32 #undef LOG_TAG
33 #define LOG_TAG "LayerInfo"
34 
35 namespace android::scheduler {
36 
37 const RefreshRateConfigs* LayerInfo::sRefreshRateConfigs = nullptr;
38 bool LayerInfo::sTraceEnabled = false;
39 
LayerInfo(const std::string & name,uid_t ownerUid,LayerHistory::LayerVoteType defaultVote)40 LayerInfo::LayerInfo(const std::string& name, uid_t ownerUid,
41                      LayerHistory::LayerVoteType defaultVote)
42       : mName(name),
43         mOwnerUid(ownerUid),
44         mDefaultVote(defaultVote),
45         mLayerVote({defaultVote, Fps(0.0f)}),
46         mRefreshRateHistory(name) {}
47 
setLastPresentTime(nsecs_t lastPresentTime,nsecs_t now,LayerUpdateType updateType,bool pendingModeChange,LayerProps props)48 void LayerInfo::setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now, LayerUpdateType updateType,
49                                    bool pendingModeChange, LayerProps props) {
50     lastPresentTime = std::max(lastPresentTime, static_cast<nsecs_t>(0));
51 
52     mLastUpdatedTime = std::max(lastPresentTime, now);
53     mLayerProps = props;
54     switch (updateType) {
55         case LayerUpdateType::AnimationTX:
56             mLastAnimationTime = std::max(lastPresentTime, now);
57             break;
58         case LayerUpdateType::SetFrameRate:
59         case LayerUpdateType::Buffer:
60             FrameTimeData frameTime = {.presentTime = lastPresentTime,
61                                        .queueTime = mLastUpdatedTime,
62                                        .pendingModeChange = pendingModeChange};
63             mFrameTimes.push_back(frameTime);
64             if (mFrameTimes.size() > HISTORY_SIZE) {
65                 mFrameTimes.pop_front();
66             }
67             break;
68     }
69 }
70 
isFrameTimeValid(const FrameTimeData & frameTime) const71 bool LayerInfo::isFrameTimeValid(const FrameTimeData& frameTime) const {
72     return frameTime.queueTime >= std::chrono::duration_cast<std::chrono::nanoseconds>(
73                                           mFrameTimeValidSince.time_since_epoch())
74                                           .count();
75 }
76 
isFrequent(nsecs_t now) const77 bool LayerInfo::isFrequent(nsecs_t now) const {
78     // If we know nothing about this layer we consider it as frequent as it might be the start
79     // of an animation.
80     if (mFrameTimes.size() < kFrequentLayerWindowSize) {
81         return true;
82     }
83 
84     // Find the first active frame
85     auto it = mFrameTimes.begin();
86     for (; it != mFrameTimes.end(); ++it) {
87         if (it->queueTime >= getActiveLayerThreshold(now)) {
88             break;
89         }
90     }
91 
92     const auto numFrames = std::distance(it, mFrameTimes.end());
93     if (numFrames < kFrequentLayerWindowSize) {
94         return false;
95     }
96 
97     // Layer is considered frequent if the average frame rate is higher than the threshold
98     const auto totalTime = mFrameTimes.back().queueTime - it->queueTime;
99     return Fps::fromPeriodNsecs(totalTime / (numFrames - 1))
100             .greaterThanOrEqualWithMargin(kMinFpsForFrequentLayer);
101 }
102 
isAnimating(nsecs_t now) const103 bool LayerInfo::isAnimating(nsecs_t now) const {
104     return mLastAnimationTime >= getActiveLayerThreshold(now);
105 }
106 
hasEnoughDataForHeuristic() const107 bool LayerInfo::hasEnoughDataForHeuristic() const {
108     // The layer had to publish at least HISTORY_SIZE or HISTORY_DURATION of updates
109     if (mFrameTimes.size() < 2) {
110         ALOGV("fewer than 2 frames recorded: %zu", mFrameTimes.size());
111         return false;
112     }
113 
114     if (!isFrameTimeValid(mFrameTimes.front())) {
115         ALOGV("stale frames still captured");
116         return false;
117     }
118 
119     const auto totalDuration = mFrameTimes.back().queueTime - mFrameTimes.front().queueTime;
120     if (mFrameTimes.size() < HISTORY_SIZE && totalDuration < HISTORY_DURATION.count()) {
121         ALOGV("not enough frames captured: %zu | %.2f seconds", mFrameTimes.size(),
122               totalDuration / 1e9f);
123         return false;
124     }
125 
126     return true;
127 }
128 
calculateAverageFrameTime() const129 std::optional<nsecs_t> LayerInfo::calculateAverageFrameTime() const {
130     // Ignore frames captured during a mode change
131     const bool isDuringModeChange =
132             std::any_of(mFrameTimes.begin(), mFrameTimes.end(),
133                         [](const auto& frame) { return frame.pendingModeChange; });
134     if (isDuringModeChange) {
135         return std::nullopt;
136     }
137 
138     const bool isMissingPresentTime =
139             std::any_of(mFrameTimes.begin(), mFrameTimes.end(),
140                         [](auto frame) { return frame.presentTime == 0; });
141     if (isMissingPresentTime && !mLastRefreshRate.reported.isValid()) {
142         // If there are no presentation timestamps and we haven't calculated
143         // one in the past then we can't calculate the refresh rate
144         return std::nullopt;
145     }
146 
147     // Calculate the average frame time based on presentation timestamps. If those
148     // doesn't exist, we look at the time the buffer was queued only. We can do that only if
149     // we calculated a refresh rate based on presentation timestamps in the past. The reason
150     // we look at the queue time is to handle cases where hwui attaches presentation timestamps
151     // when implementing render ahead for specific refresh rates. When hwui no longer provides
152     // presentation timestamps we look at the queue time to see if the current refresh rate still
153     // matches the content.
154 
155     auto getFrameTime = isMissingPresentTime ? [](FrameTimeData data) { return data.queueTime; }
156                                              : [](FrameTimeData data) { return data.presentTime; };
157 
158     nsecs_t totalDeltas = 0;
159     int numDeltas = 0;
160     auto prevFrame = mFrameTimes.begin();
161     for (auto it = mFrameTimes.begin() + 1; it != mFrameTimes.end(); ++it) {
162         const auto currDelta = getFrameTime(*it) - getFrameTime(*prevFrame);
163         if (currDelta < kMinPeriodBetweenFrames) {
164             // Skip this frame, but count the delta into the next frame
165             continue;
166         }
167 
168         prevFrame = it;
169 
170         if (currDelta > kMaxPeriodBetweenFrames) {
171             // Skip this frame and the current delta.
172             continue;
173         }
174 
175         totalDeltas += currDelta;
176         numDeltas++;
177     }
178 
179     if (numDeltas == 0) {
180         return std::nullopt;
181     }
182 
183     const auto averageFrameTime = static_cast<double>(totalDeltas) / static_cast<double>(numDeltas);
184     return static_cast<nsecs_t>(averageFrameTime);
185 }
186 
calculateRefreshRateIfPossible(nsecs_t now)187 std::optional<Fps> LayerInfo::calculateRefreshRateIfPossible(nsecs_t now) {
188     static constexpr float MARGIN = 1.0f; // 1Hz
189     if (!hasEnoughDataForHeuristic()) {
190         ALOGV("Not enough data");
191         return std::nullopt;
192     }
193 
194     const auto averageFrameTime = calculateAverageFrameTime();
195     if (averageFrameTime.has_value()) {
196         const auto refreshRate = Fps::fromPeriodNsecs(*averageFrameTime);
197         const bool refreshRateConsistent = mRefreshRateHistory.add(refreshRate, now);
198         if (refreshRateConsistent) {
199             const auto knownRefreshRate =
200                     sRefreshRateConfigs->findClosestKnownFrameRate(refreshRate);
201 
202             // To avoid oscillation, use the last calculated refresh rate if it is
203             // close enough
204             if (std::abs(mLastRefreshRate.calculated.getValue() - refreshRate.getValue()) >
205                         MARGIN &&
206                 !mLastRefreshRate.reported.equalsWithMargin(knownRefreshRate)) {
207                 mLastRefreshRate.calculated = refreshRate;
208                 mLastRefreshRate.reported = knownRefreshRate;
209             }
210 
211             ALOGV("%s %s rounded to nearest known frame rate %s", mName.c_str(),
212                   to_string(refreshRate).c_str(), to_string(mLastRefreshRate.reported).c_str());
213         } else {
214             ALOGV("%s Not stable (%s) returning last known frame rate %s", mName.c_str(),
215                   to_string(refreshRate).c_str(), to_string(mLastRefreshRate.reported).c_str());
216         }
217     }
218 
219     return mLastRefreshRate.reported.isValid() ? std::make_optional(mLastRefreshRate.reported)
220                                                : std::nullopt;
221 }
222 
getRefreshRateVote(nsecs_t now)223 LayerInfo::LayerVote LayerInfo::getRefreshRateVote(nsecs_t now) {
224     if (mLayerVote.type != LayerHistory::LayerVoteType::Heuristic) {
225         ALOGV("%s voted %d ", mName.c_str(), static_cast<int>(mLayerVote.type));
226         return mLayerVote;
227     }
228 
229     if (isAnimating(now)) {
230         ALOGV("%s is animating", mName.c_str());
231         mLastRefreshRate.animatingOrInfrequent = true;
232         return {LayerHistory::LayerVoteType::Max, Fps(0.0f)};
233     }
234 
235     if (!isFrequent(now)) {
236         ALOGV("%s is infrequent", mName.c_str());
237         mLastRefreshRate.animatingOrInfrequent = true;
238         // Infrequent layers vote for mininal refresh rate for
239         // battery saving purposes and also to prevent b/135718869.
240         return {LayerHistory::LayerVoteType::Min, Fps(0.0f)};
241     }
242 
243     // If the layer was previously tagged as animating or infrequent, we clear
244     // the history as it is likely the layer just changed its behavior
245     // and we should not look at stale data
246     if (mLastRefreshRate.animatingOrInfrequent) {
247         clearHistory(now);
248     }
249 
250     auto refreshRate = calculateRefreshRateIfPossible(now);
251     if (refreshRate.has_value()) {
252         ALOGV("%s calculated refresh rate: %s", mName.c_str(), to_string(*refreshRate).c_str());
253         return {LayerHistory::LayerVoteType::Heuristic, refreshRate.value()};
254     }
255 
256     ALOGV("%s Max (can't resolve refresh rate)", mName.c_str());
257     return {LayerHistory::LayerVoteType::Max, Fps(0.0f)};
258 }
259 
getTraceTag(android::scheduler::LayerHistory::LayerVoteType type) const260 const char* LayerInfo::getTraceTag(android::scheduler::LayerHistory::LayerVoteType type) const {
261     if (mTraceTags.count(type) == 0) {
262         const auto tag = "LFPS " + mName + " " + RefreshRateConfigs::layerVoteTypeString(type);
263         mTraceTags.emplace(type, tag);
264     }
265 
266     return mTraceTags.at(type).c_str();
267 }
268 
269 LayerInfo::RefreshRateHistory::HeuristicTraceTagData
makeHeuristicTraceTagData() const270 LayerInfo::RefreshRateHistory::makeHeuristicTraceTagData() const {
271     const std::string prefix = "LFPS ";
272     const std::string suffix = "Heuristic ";
273     return {.min = prefix + mName + suffix + "min",
274             .max = prefix + mName + suffix + "max",
275             .consistent = prefix + mName + suffix + "consistent",
276             .average = prefix + mName + suffix + "average"};
277 }
278 
clear()279 void LayerInfo::RefreshRateHistory::clear() {
280     mRefreshRates.clear();
281 }
282 
add(Fps refreshRate,nsecs_t now)283 bool LayerInfo::RefreshRateHistory::add(Fps refreshRate, nsecs_t now) {
284     mRefreshRates.push_back({refreshRate, now});
285     while (mRefreshRates.size() >= HISTORY_SIZE ||
286            now - mRefreshRates.front().timestamp > HISTORY_DURATION.count()) {
287         mRefreshRates.pop_front();
288     }
289 
290     if (CC_UNLIKELY(sTraceEnabled)) {
291         if (!mHeuristicTraceTagData.has_value()) {
292             mHeuristicTraceTagData = makeHeuristicTraceTagData();
293         }
294 
295         ATRACE_INT(mHeuristicTraceTagData->average.c_str(), refreshRate.getIntValue());
296     }
297 
298     return isConsistent();
299 }
300 
isConsistent() const301 bool LayerInfo::RefreshRateHistory::isConsistent() const {
302     if (mRefreshRates.empty()) return true;
303 
304     const auto max = std::max_element(mRefreshRates.begin(), mRefreshRates.end());
305     const auto min = std::min_element(mRefreshRates.begin(), mRefreshRates.end());
306     const auto consistent =
307             max->refreshRate.getValue() - min->refreshRate.getValue() < MARGIN_CONSISTENT_FPS;
308 
309     if (CC_UNLIKELY(sTraceEnabled)) {
310         if (!mHeuristicTraceTagData.has_value()) {
311             mHeuristicTraceTagData = makeHeuristicTraceTagData();
312         }
313 
314         ATRACE_INT(mHeuristicTraceTagData->max.c_str(), max->refreshRate.getIntValue());
315         ATRACE_INT(mHeuristicTraceTagData->min.c_str(), min->refreshRate.getIntValue());
316         ATRACE_INT(mHeuristicTraceTagData->consistent.c_str(), consistent);
317     }
318 
319     return consistent;
320 }
321 
322 } // namespace android::scheduler
323 
324 // TODO(b/129481165): remove the #pragma below and fix conversion issues
325 #pragma clang diagnostic pop // ignored "-Wextra"