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 #include <ftl/enum.h>
32 #include <gui/TraceUtils.h>
33
34 #undef LOG_TAG
35 #define LOG_TAG "LayerInfo"
36
37 namespace android::scheduler {
38
39 bool LayerInfo::sTraceEnabled = false;
40
LayerInfo(const std::string & name,uid_t ownerUid,LayerHistory::LayerVoteType defaultVote)41 LayerInfo::LayerInfo(const std::string& name, uid_t ownerUid,
42 LayerHistory::LayerVoteType defaultVote)
43 : mName(name),
44 mOwnerUid(ownerUid),
45 mDefaultVote(defaultVote),
46 mLayerVote({defaultVote, Fps()}),
47 mLayerProps(std::make_unique<LayerProps>()),
48 mRefreshRateHistory(name) {
49 ;
50 }
51
setLastPresentTime(nsecs_t lastPresentTime,nsecs_t now,LayerUpdateType updateType,bool pendingModeChange,const LayerProps & props)52 void LayerInfo::setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now, LayerUpdateType updateType,
53 bool pendingModeChange, const LayerProps& props) {
54 lastPresentTime = std::max(lastPresentTime, static_cast<nsecs_t>(0));
55
56 mLastUpdatedTime = std::max(lastPresentTime, now);
57 *mLayerProps = props;
58 switch (updateType) {
59 case LayerUpdateType::AnimationTX:
60 mLastAnimationTime = std::max(lastPresentTime, now);
61 break;
62 case LayerUpdateType::SetFrameRate:
63 case LayerUpdateType::Buffer:
64 FrameTimeData frameTime = {.presentTime = lastPresentTime,
65 .queueTime = mLastUpdatedTime,
66 .pendingModeChange = pendingModeChange,
67 .isSmallDirty = props.isSmallDirty};
68 mFrameTimes.push_back(frameTime);
69 if (mFrameTimes.size() > HISTORY_SIZE) {
70 mFrameTimes.pop_front();
71 }
72 break;
73 }
74 }
75
isFrameTimeValid(const FrameTimeData & frameTime) const76 bool LayerInfo::isFrameTimeValid(const FrameTimeData& frameTime) const {
77 return frameTime.queueTime >= std::chrono::duration_cast<std::chrono::nanoseconds>(
78 mFrameTimeValidSince.time_since_epoch())
79 .count();
80 }
81
isFrequent(nsecs_t now) const82 LayerInfo::Frequent LayerInfo::isFrequent(nsecs_t now) const {
83 // If we know nothing about this layer (e.g. after touch event),
84 // we consider it as frequent as it might be the start of an animation.
85 if (mFrameTimes.size() < kFrequentLayerWindowSize) {
86 return {/* isFrequent */ true, /* clearHistory */ false, /* isConclusive */ true};
87 }
88
89 // Non-active layers are also infrequent
90 if (mLastUpdatedTime < getActiveLayerThreshold(now)) {
91 return {/* isFrequent */ false, /* clearHistory */ false, /* isConclusive */ true};
92 }
93
94 // We check whether we can classify this layer as frequent or infrequent:
95 // - frequent: a layer posted kFrequentLayerWindowSize within
96 // kMaxPeriodForFrequentLayerNs of each other.
97 // - infrequent: a layer posted kFrequentLayerWindowSize with longer
98 // gaps than kFrequentLayerWindowSize.
99 // If we can't determine the layer classification yet, we return the last
100 // classification.
101 bool isFrequent = true;
102 bool isInfrequent = true;
103 int32_t smallDirtyCount = 0;
104 const auto n = mFrameTimes.size() - 1;
105 for (size_t i = 0; i < kFrequentLayerWindowSize - 1; i++) {
106 if (mFrameTimes[n - i].queueTime - mFrameTimes[n - i - 1].queueTime <
107 kMaxPeriodForFrequentLayerNs.count()) {
108 isInfrequent = false;
109 if (mFrameTimes[n - i].presentTime == 0 && mFrameTimes[n - i].isSmallDirty) {
110 smallDirtyCount++;
111 }
112 } else {
113 isFrequent = false;
114 }
115 }
116
117 // Vote the small dirty when a layer contains at least HISTORY_SIZE of small dirty updates.
118 bool isSmallDirty = false;
119 if (smallDirtyCount >= kNumSmallDirtyThreshold) {
120 if (mLastSmallDirtyCount >= HISTORY_SIZE) {
121 isSmallDirty = true;
122 } else {
123 mLastSmallDirtyCount++;
124 }
125 } else {
126 mLastSmallDirtyCount = 0;
127 }
128
129 if (isFrequent || isInfrequent) {
130 // If the layer was previously inconclusive, we clear
131 // the history as indeterminate layers changed to frequent,
132 // and we should not look at the stale data.
133 return {isFrequent, isFrequent && !mIsFrequencyConclusive, /* isConclusive */ true,
134 isSmallDirty};
135 }
136
137 // If we can't determine whether the layer is frequent or not, we return
138 // the last known classification and mark the layer frequency as inconclusive.
139 isFrequent = !mLastRefreshRate.infrequent;
140
141 // If the layer was previously tagged as animating, we clear
142 // the history as it is likely the layer just changed its behavior,
143 // and we should not look at stale data.
144 return {isFrequent, isFrequent && mLastRefreshRate.animating, /* isConclusive */ false};
145 }
146
getFps(nsecs_t now) const147 Fps LayerInfo::getFps(nsecs_t now) const {
148 // Find the first active frame
149 auto it = mFrameTimes.begin();
150 for (; it != mFrameTimes.end(); ++it) {
151 if (it->queueTime >= getActiveLayerThreshold(now)) {
152 break;
153 }
154 }
155
156 const auto numFrames = std::distance(it, mFrameTimes.end());
157 if (numFrames < kFrequentLayerWindowSize) {
158 return Fps();
159 }
160
161 // Layer is considered frequent if the average frame rate is higher than the threshold
162 const auto totalTime = mFrameTimes.back().queueTime - it->queueTime;
163 return Fps::fromPeriodNsecs(totalTime / (numFrames - 1));
164 }
165
isAnimating(nsecs_t now) const166 bool LayerInfo::isAnimating(nsecs_t now) const {
167 return mLastAnimationTime >= getActiveLayerThreshold(now);
168 }
169
hasEnoughDataForHeuristic() const170 bool LayerInfo::hasEnoughDataForHeuristic() const {
171 // The layer had to publish at least HISTORY_SIZE or HISTORY_DURATION of updates
172 if (mFrameTimes.size() < 2) {
173 ALOGV("fewer than 2 frames recorded: %zu", mFrameTimes.size());
174 return false;
175 }
176
177 if (!isFrameTimeValid(mFrameTimes.front())) {
178 ALOGV("stale frames still captured");
179 return false;
180 }
181
182 const auto totalDuration = mFrameTimes.back().queueTime - mFrameTimes.front().queueTime;
183 if (mFrameTimes.size() < HISTORY_SIZE && totalDuration < HISTORY_DURATION.count()) {
184 ALOGV("not enough frames captured: %zu | %.2f seconds", mFrameTimes.size(),
185 totalDuration / 1e9f);
186 return false;
187 }
188
189 return true;
190 }
191
calculateAverageFrameTime() const192 std::optional<nsecs_t> LayerInfo::calculateAverageFrameTime() const {
193 // Ignore frames captured during a mode change
194 const bool isDuringModeChange =
195 std::any_of(mFrameTimes.begin(), mFrameTimes.end(),
196 [](const auto& frame) { return frame.pendingModeChange; });
197 if (isDuringModeChange) {
198 return std::nullopt;
199 }
200
201 const bool isMissingPresentTime =
202 std::any_of(mFrameTimes.begin(), mFrameTimes.end(),
203 [](auto frame) { return frame.presentTime == 0; });
204 if (isMissingPresentTime && !mLastRefreshRate.reported.isValid()) {
205 // If there are no presentation timestamps and we haven't calculated
206 // one in the past then we can't calculate the refresh rate
207 return std::nullopt;
208 }
209
210 // Calculate the average frame time based on presentation timestamps. If those
211 // doesn't exist, we look at the time the buffer was queued only. We can do that only if
212 // we calculated a refresh rate based on presentation timestamps in the past. The reason
213 // we look at the queue time is to handle cases where hwui attaches presentation timestamps
214 // when implementing render ahead for specific refresh rates. When hwui no longer provides
215 // presentation timestamps we look at the queue time to see if the current refresh rate still
216 // matches the content.
217
218 auto getFrameTime = isMissingPresentTime ? [](FrameTimeData data) { return data.queueTime; }
219 : [](FrameTimeData data) { return data.presentTime; };
220
221 nsecs_t totalDeltas = 0;
222 int numDeltas = 0;
223 int32_t smallDirtyCount = 0;
224 auto prevFrame = mFrameTimes.begin();
225 for (auto it = mFrameTimes.begin() + 1; it != mFrameTimes.end(); ++it) {
226 const auto currDelta = getFrameTime(*it) - getFrameTime(*prevFrame);
227 if (currDelta < kMinPeriodBetweenFrames) {
228 // Skip this frame, but count the delta into the next frame
229 continue;
230 }
231
232 // If this is a small area update, we don't want to consider it for calculating the average
233 // frame time. Instead, we let the bigger frame updates to drive the calculation.
234 if (it->isSmallDirty && currDelta < kMinPeriodBetweenSmallDirtyFrames) {
235 smallDirtyCount++;
236 continue;
237 }
238
239 prevFrame = it;
240
241 if (currDelta > kMaxPeriodBetweenFrames) {
242 // Skip this frame and the current delta.
243 continue;
244 }
245
246 totalDeltas += currDelta;
247 numDeltas++;
248 }
249
250 if (smallDirtyCount > 0) {
251 ATRACE_FORMAT_INSTANT("small dirty = %" PRIu32, smallDirtyCount);
252 }
253
254 if (numDeltas == 0) {
255 return std::nullopt;
256 }
257
258 const auto averageFrameTime = static_cast<double>(totalDeltas) / static_cast<double>(numDeltas);
259 return static_cast<nsecs_t>(averageFrameTime);
260 }
261
calculateRefreshRateIfPossible(const RefreshRateSelector & selector,nsecs_t now)262 std::optional<Fps> LayerInfo::calculateRefreshRateIfPossible(const RefreshRateSelector& selector,
263 nsecs_t now) {
264 ATRACE_CALL();
265 static constexpr float MARGIN = 1.0f; // 1Hz
266 if (!hasEnoughDataForHeuristic()) {
267 ALOGV("Not enough data");
268 return std::nullopt;
269 }
270
271 if (const auto averageFrameTime = calculateAverageFrameTime()) {
272 const auto refreshRate = Fps::fromPeriodNsecs(*averageFrameTime);
273 const bool refreshRateConsistent = mRefreshRateHistory.add(refreshRate, now);
274 if (refreshRateConsistent) {
275 const auto knownRefreshRate = selector.findClosestKnownFrameRate(refreshRate);
276 using fps_approx_ops::operator!=;
277
278 // To avoid oscillation, use the last calculated refresh rate if it is close enough.
279 if (std::abs(mLastRefreshRate.calculated.getValue() - refreshRate.getValue()) >
280 MARGIN &&
281 mLastRefreshRate.reported != knownRefreshRate) {
282 mLastRefreshRate.calculated = refreshRate;
283 mLastRefreshRate.reported = knownRefreshRate;
284 }
285
286 ALOGV("%s %s rounded to nearest known frame rate %s", mName.c_str(),
287 to_string(refreshRate).c_str(), to_string(mLastRefreshRate.reported).c_str());
288 } else {
289 ALOGV("%s Not stable (%s) returning last known frame rate %s", mName.c_str(),
290 to_string(refreshRate).c_str(), to_string(mLastRefreshRate.reported).c_str());
291 }
292 }
293
294 return mLastRefreshRate.reported.isValid() ? std::make_optional(mLastRefreshRate.reported)
295 : std::nullopt;
296 }
297
getRefreshRateVote(const RefreshRateSelector & selector,nsecs_t now)298 LayerInfo::LayerVote LayerInfo::getRefreshRateVote(const RefreshRateSelector& selector,
299 nsecs_t now) {
300 ATRACE_CALL();
301 if (mLayerVote.type != LayerHistory::LayerVoteType::Heuristic) {
302 ALOGV("%s voted %d ", mName.c_str(), static_cast<int>(mLayerVote.type));
303 return mLayerVote;
304 }
305
306 if (isAnimating(now)) {
307 ATRACE_FORMAT_INSTANT("animating");
308 ALOGV("%s is animating", mName.c_str());
309 mLastRefreshRate.animating = true;
310 return {LayerHistory::LayerVoteType::Max, Fps()};
311 }
312
313 const LayerInfo::Frequent frequent = isFrequent(now);
314 mIsFrequencyConclusive = frequent.isConclusive;
315 if (!frequent.isFrequent) {
316 ATRACE_FORMAT_INSTANT("infrequent");
317 ALOGV("%s is infrequent", mName.c_str());
318 mLastRefreshRate.infrequent = true;
319 mLastSmallDirtyCount = 0;
320 // Infrequent layers vote for minimal refresh rate for
321 // battery saving purposes and also to prevent b/135718869.
322 return {LayerHistory::LayerVoteType::Min, Fps()};
323 }
324
325 if (frequent.clearHistory) {
326 clearHistory(now);
327 }
328
329 // Return no vote if the recent frames are small dirty.
330 if (frequent.isSmallDirty && !mLastRefreshRate.reported.isValid()) {
331 ATRACE_FORMAT_INSTANT("NoVote (small dirty)");
332 ALOGV("%s is small dirty", mName.c_str());
333 return {LayerHistory::LayerVoteType::NoVote, Fps()};
334 }
335
336 auto refreshRate = calculateRefreshRateIfPossible(selector, now);
337 if (refreshRate.has_value()) {
338 ALOGV("%s calculated refresh rate: %s", mName.c_str(), to_string(*refreshRate).c_str());
339 return {LayerHistory::LayerVoteType::Heuristic, refreshRate.value()};
340 }
341
342 ALOGV("%s Max (can't resolve refresh rate)", mName.c_str());
343 return {LayerHistory::LayerVoteType::Max, Fps()};
344 }
345
getTraceTag(LayerHistory::LayerVoteType type) const346 const char* LayerInfo::getTraceTag(LayerHistory::LayerVoteType type) const {
347 if (mTraceTags.count(type) == 0) {
348 auto tag = "LFPS " + mName + " " + ftl::enum_string(type);
349 mTraceTags.emplace(type, std::move(tag));
350 }
351
352 return mTraceTags.at(type).c_str();
353 }
354
getSetFrameRateVote() const355 LayerInfo::FrameRate LayerInfo::getSetFrameRateVote() const {
356 return mLayerProps->setFrameRateVote;
357 }
358
isVisible() const359 bool LayerInfo::isVisible() const {
360 return mLayerProps->visible;
361 }
362
getFrameRateSelectionPriority() const363 int32_t LayerInfo::getFrameRateSelectionPriority() const {
364 return mLayerProps->frameRateSelectionPriority;
365 }
366
getBounds() const367 FloatRect LayerInfo::getBounds() const {
368 return mLayerProps->bounds;
369 }
370
getTransform() const371 ui::Transform LayerInfo::getTransform() const {
372 return mLayerProps->transform;
373 }
374
375 LayerInfo::RefreshRateHistory::HeuristicTraceTagData
makeHeuristicTraceTagData() const376 LayerInfo::RefreshRateHistory::makeHeuristicTraceTagData() const {
377 const std::string prefix = "LFPS ";
378 const std::string suffix = "Heuristic ";
379 return {.min = prefix + mName + suffix + "min",
380 .max = prefix + mName + suffix + "max",
381 .consistent = prefix + mName + suffix + "consistent",
382 .average = prefix + mName + suffix + "average"};
383 }
384
clear()385 void LayerInfo::RefreshRateHistory::clear() {
386 mRefreshRates.clear();
387 }
388
add(Fps refreshRate,nsecs_t now)389 bool LayerInfo::RefreshRateHistory::add(Fps refreshRate, nsecs_t now) {
390 mRefreshRates.push_back({refreshRate, now});
391 while (mRefreshRates.size() >= HISTORY_SIZE ||
392 now - mRefreshRates.front().timestamp > HISTORY_DURATION.count()) {
393 mRefreshRates.pop_front();
394 }
395
396 if (CC_UNLIKELY(sTraceEnabled)) {
397 if (!mHeuristicTraceTagData.has_value()) {
398 mHeuristicTraceTagData = makeHeuristicTraceTagData();
399 }
400
401 ATRACE_INT(mHeuristicTraceTagData->average.c_str(), refreshRate.getIntValue());
402 }
403
404 return isConsistent();
405 }
406
isConsistent() const407 bool LayerInfo::RefreshRateHistory::isConsistent() const {
408 if (mRefreshRates.empty()) return true;
409
410 const auto [min, max] =
411 std::minmax_element(mRefreshRates.begin(), mRefreshRates.end(),
412 [](const auto& lhs, const auto& rhs) {
413 return isStrictlyLess(lhs.refreshRate, rhs.refreshRate);
414 });
415
416 const bool consistent =
417 max->refreshRate.getValue() - min->refreshRate.getValue() < MARGIN_CONSISTENT_FPS;
418
419 if (CC_UNLIKELY(sTraceEnabled)) {
420 if (!mHeuristicTraceTagData.has_value()) {
421 mHeuristicTraceTagData = makeHeuristicTraceTagData();
422 }
423
424 ATRACE_INT(mHeuristicTraceTagData->max.c_str(), max->refreshRate.getIntValue());
425 ATRACE_INT(mHeuristicTraceTagData->min.c_str(), min->refreshRate.getIntValue());
426 ATRACE_INT(mHeuristicTraceTagData->consistent.c_str(), consistent);
427 }
428
429 return consistent;
430 }
431
432 } // namespace android::scheduler
433
434 // TODO(b/129481165): remove the #pragma below and fix conversion issues
435 #pragma clang diagnostic pop // ignored "-Wextra"
436