1 /*
2 * Copyright 2021 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 // #define LOG_NDEBUG 0
18
19 #undef LOG_TAG
20 #define LOG_TAG "Planner"
21
22 #include <compositionengine/impl/planner/Predictor.h>
23
24 namespace android::compositionengine::impl::planner {
25
getApproximateMatch(const std::vector<const LayerState * > & other) const26 std::optional<LayerStack::ApproximateMatch> LayerStack::getApproximateMatch(
27 const std::vector<const LayerState*>& other) const {
28 // Differing numbers of layers are never an approximate match
29 if (mLayers.size() != other.size()) {
30 return std::nullopt;
31 }
32
33 std::optional<ApproximateMatch> approximateMatch = {};
34 for (size_t i = 0; i < mLayers.size(); ++i) {
35 // Skip identical layers
36 if (mLayers[i].getHash() == other[i]->getHash()) {
37 continue;
38 }
39
40 // Skip layers where both are client-composited, since that doesn't change the
41 // composition plan
42 if (mLayers[i].getCompositionType() ==
43 aidl::android::hardware::graphics::composer3::Composition::CLIENT &&
44 other[i]->getCompositionType() ==
45 aidl::android::hardware::graphics::composer3::Composition::CLIENT) {
46 continue;
47 }
48
49 // If layers differ in composition type, their stacks are too different
50 if (mLayers[i].getCompositionType() != other[i]->getCompositionType()) {
51 return std::nullopt;
52 }
53
54 // If layers are not identical, but we already detected a prior approximate match for a
55 // previous layer, the LayerStacks differ by too much, so return nothing
56 if (approximateMatch) {
57 return std::nullopt;
58 }
59
60 ftl::Flags<LayerStateField> differingFields = mLayers[i].getDifferingFields(*other[i]);
61
62 // If we don't find an approximate match on this layer, then the LayerStacks differ
63 // by too much, so return nothing
64 const int differingFieldCount = __builtin_popcount(differingFields.get());
65 if (differingFieldCount <= kMaxDifferingFields) {
66 approximateMatch = ApproximateMatch{
67 .differingIndex = i,
68 .differingFields = differingFields,
69 };
70 } else {
71 return std::nullopt;
72 }
73 }
74
75 if (approximateMatch) {
76 return approximateMatch;
77 }
78
79 // If we make it through the layer-by-layer comparison without an approximate match,
80 // it means that all layers were either identical or had client-composited layers in common,
81 // which don't affect the composition strategy, so return a successful result with
82 // no differences.
83 return ApproximateMatch{
84 .differingIndex = 0,
85 .differingFields = {},
86 };
87 }
88
fromString(const std::string & string)89 std::optional<Plan> Plan::fromString(const std::string& string) {
90 Plan plan;
91 for (char c : string) {
92 switch (c) {
93 case 'C':
94 plan.addLayerType(
95 aidl::android::hardware::graphics::composer3::Composition::CLIENT);
96 continue;
97 case 'U':
98 plan.addLayerType(
99 aidl::android::hardware::graphics::composer3::Composition::CURSOR);
100 continue;
101 case 'D':
102 plan.addLayerType(
103 aidl::android::hardware::graphics::composer3::Composition::DEVICE);
104 continue;
105 case 'I':
106 plan.addLayerType(
107 aidl::android::hardware::graphics::composer3::Composition::INVALID);
108 continue;
109 case 'B':
110 plan.addLayerType(
111 aidl::android::hardware::graphics::composer3::Composition::SIDEBAND);
112 continue;
113 case 'S':
114 plan.addLayerType(
115 aidl::android::hardware::graphics::composer3::Composition::SOLID_COLOR);
116 continue;
117 case 'A':
118 plan.addLayerType(aidl::android::hardware::graphics::composer3::Composition::
119 DISPLAY_DECORATION);
120 continue;
121 default:
122 return std::nullopt;
123 }
124 }
125 return plan;
126 }
127
to_string(const Plan & plan)128 std::string to_string(const Plan& plan) {
129 std::string result;
130 for (auto type : plan.mLayerTypes) {
131 switch (type) {
132 case aidl::android::hardware::graphics::composer3::Composition::CLIENT:
133 result.append("C");
134 break;
135 case aidl::android::hardware::graphics::composer3::Composition::CURSOR:
136 result.append("U");
137 break;
138 case aidl::android::hardware::graphics::composer3::Composition::DEVICE:
139 result.append("D");
140 break;
141 case aidl::android::hardware::graphics::composer3::Composition::INVALID:
142 result.append("I");
143 break;
144 case aidl::android::hardware::graphics::composer3::Composition::SIDEBAND:
145 result.append("B");
146 break;
147 case aidl::android::hardware::graphics::composer3::Composition::SOLID_COLOR:
148 result.append("S");
149 break;
150 case aidl::android::hardware::graphics::composer3::Composition::DISPLAY_DECORATION:
151 // A for "Alpha", since the decoration is an alpha layer.
152 result.append("A");
153 break;
154 case aidl::android::hardware::graphics::composer3::Composition::REFRESH_RATE_INDICATOR:
155 // R for "Refresh", since the layer is Refresh rate overlay.
156 result.append("R");
157 break;
158 }
159 }
160 return result;
161 }
162
dump(std::string & result) const163 void Prediction::dump(std::string& result) const {
164 result.append(to_string(mPlan));
165 result.append(" [Exact ");
166 mExactStats.dump(result);
167 result.append("] [Approximate ");
168 mApproximateStats.dump(result);
169 result.append("]");
170 }
171
getPredictedPlan(const std::vector<const LayerState * > & layers,NonBufferHash hash) const172 std::optional<Predictor::PredictedPlan> Predictor::getPredictedPlan(
173 const std::vector<const LayerState*>& layers, NonBufferHash hash) const {
174 // First check for an exact match
175 if (std::optional<Plan> exactMatch = getExactMatch(hash); exactMatch) {
176 ALOGV("[%s] Found an exact match for %zx", __func__, hash);
177 return PredictedPlan{.hash = hash, .plan = *exactMatch, .type = Prediction::Type::Exact};
178 }
179
180 // If only a hash was passed in for a layer stack with a cached set, don't perform
181 // approximate matches and return early
182 if (layers.empty()) {
183 ALOGV("[%s] Only hash was passed, but no exact match was found", __func__);
184 return std::nullopt;
185 }
186
187 // Then check for approximate matches
188 if (std::optional<NonBufferHash> approximateMatch = getApproximateMatch(layers);
189 approximateMatch) {
190 ALOGV("[%s] Found an approximate match for %zx", __func__, *approximateMatch);
191 const Prediction& prediction = getPrediction(*approximateMatch);
192 return PredictedPlan{.hash = *approximateMatch,
193 .plan = prediction.getPlan(),
194 .type = Prediction::Type::Approximate};
195 }
196
197 return std::nullopt;
198 }
199
recordResult(std::optional<PredictedPlan> predictedPlan,NonBufferHash flattenedHash,const std::vector<const LayerState * > & layers,bool hasSkippedLayers,Plan result)200 void Predictor::recordResult(std::optional<PredictedPlan> predictedPlan,
201 NonBufferHash flattenedHash,
202 const std::vector<const LayerState*>& layers, bool hasSkippedLayers,
203 Plan result) {
204 if (predictedPlan) {
205 recordPredictedResult(*predictedPlan, layers, std::move(result));
206 return;
207 }
208
209 ++mMissCount;
210
211 if (!hasSkippedLayers && findSimilarPrediction(layers, result)) {
212 return;
213 }
214
215 ALOGV("[%s] Adding novel candidate %zx", __func__, flattenedHash);
216 mCandidates.emplace_front(flattenedHash, Prediction(layers, result));
217 if (mCandidates.size() > MAX_CANDIDATES) {
218 mCandidates.pop_back();
219 }
220 }
221
dump(std::string & result) const222 void Predictor::dump(std::string& result) const {
223 result.append("Predictor state:\n");
224
225 const size_t hitCount = mExactHitCount + mApproximateHitCount;
226 const size_t totalAttempts = hitCount + mMissCount;
227 base::StringAppendF(&result, "Global non-skipped hit rate: %.2f%% (%zd/%zd)\n",
228 100.0f * hitCount / totalAttempts, hitCount, totalAttempts);
229 base::StringAppendF(&result, " Exact hits: %zd\n", mExactHitCount);
230 base::StringAppendF(&result, " Approximate hits: %zd\n", mApproximateHitCount);
231 base::StringAppendF(&result, " Misses: %zd\n\n", mMissCount);
232
233 dumpPredictionsByFrequency(result);
234 }
235
compareLayerStacks(NonBufferHash leftHash,NonBufferHash rightHash,std::string & result) const236 void Predictor::compareLayerStacks(NonBufferHash leftHash, NonBufferHash rightHash,
237 std::string& result) const {
238 const auto& [leftPredictionEntry, rightPredictionEntry] =
239 std::make_tuple(mPredictions.find(leftHash), mPredictions.find(rightHash));
240 if (leftPredictionEntry == mPredictions.end()) {
241 base::StringAppendF(&result, "No prediction found for %zx\n", leftHash);
242 return;
243 }
244 if (rightPredictionEntry == mPredictions.end()) {
245 base::StringAppendF(&result, "No prediction found for %zx\n", rightHash);
246 return;
247 }
248
249 base::StringAppendF(&result,
250 "Comparing %-16zx %-16zx\n",
251 leftHash, rightHash);
252
253 const auto& [leftPrediction, rightPrediction] =
254 std::make_tuple(leftPredictionEntry->second, rightPredictionEntry->second);
255 const auto& [leftStack, rightStack] = std::make_tuple(leftPrediction.getExampleLayerStack(),
256 rightPrediction.getExampleLayerStack());
257 leftStack.compare(rightStack, result);
258 }
259
describeLayerStack(NonBufferHash hash,std::string & result) const260 void Predictor::describeLayerStack(NonBufferHash hash, std::string& result) const {
261 base::StringAppendF(&result, "Describing %zx:\n\n", hash);
262
263 if (const auto predictionsEntry = mPredictions.find(hash);
264 predictionsEntry != mPredictions.cend()) {
265 const auto& [hash, prediction] = *predictionsEntry;
266
267 prediction.getExampleLayerStack().dump(result);
268
269 result.append("Prediction: ");
270 prediction.dump(result);
271 result.append("\n");
272 } else {
273 result.append("No predictions found\n");
274 }
275 }
276
listSimilarStacks(Plan plan,std::string & result) const277 void Predictor::listSimilarStacks(Plan plan, std::string& result) const {
278 base::StringAppendF(&result, "Similar stacks for plan %s:\n", to_string(plan).c_str());
279
280 if (const auto similarStacksEntry = mSimilarStacks.find(plan);
281 similarStacksEntry != mSimilarStacks.end()) {
282 const auto& [_, similarStacks] = *similarStacksEntry;
283 for (NonBufferHash hash : similarStacks) {
284 base::StringAppendF(&result, "\nPrediction hash %zx:\n", hash);
285 const Prediction& prediction = mPredictions.at(hash);
286 prediction.getExampleLayerStack().dumpLayerNames(result);
287 }
288 } else {
289 result.append("No similar stacks found\n");
290 }
291 }
292
getPrediction(NonBufferHash hash) const293 const Prediction& Predictor::getPrediction(NonBufferHash hash) const {
294 if (const auto predictionEntry = mPredictions.find(hash);
295 predictionEntry != mPredictions.end()) {
296 const auto& [_, prediction] = *predictionEntry;
297 return prediction;
298 } else {
299 const auto candidateEntry = getCandidateEntryByHash(hash);
300 ALOGE_IF(candidateEntry == mCandidates.cend(),
301 "Hash should have been found in either predictions or candidates");
302 const auto& [_, prediction] = *candidateEntry;
303 return prediction;
304 }
305 }
306
getPrediction(NonBufferHash hash)307 Prediction& Predictor::getPrediction(NonBufferHash hash) {
308 return const_cast<Prediction&>(const_cast<const Predictor*>(this)->getPrediction(hash));
309 }
310
getExactMatch(NonBufferHash hash) const311 std::optional<Plan> Predictor::getExactMatch(NonBufferHash hash) const {
312 const Prediction* match = nullptr;
313 if (const auto predictionEntry = mPredictions.find(hash);
314 predictionEntry != mPredictions.end()) {
315 const auto& [hash, prediction] = *predictionEntry;
316 match = &prediction;
317 } else if (const auto candidateEntry = getCandidateEntryByHash(hash);
318 candidateEntry != mCandidates.cend()) {
319 match = &(candidateEntry->prediction);
320 }
321
322 if (match == nullptr) {
323 return std::nullopt;
324 }
325
326 if (match->getMissCount(Prediction::Type::Exact) != 0) {
327 ALOGV("[%s] Skipping exact match for %zx because of prior miss", __func__, hash);
328 return std::nullopt;
329 }
330
331 return match->getPlan();
332 }
333
getApproximateMatch(const std::vector<const LayerState * > & layers) const334 std::optional<NonBufferHash> Predictor::getApproximateMatch(
335 const std::vector<const LayerState*>& layers) const {
336 const auto approximateStackMatches = [&](const ApproximateStack& approximateStack) {
337 const auto& exampleStack = mPredictions.at(approximateStack.hash).getExampleLayerStack();
338 if (const auto approximateMatchOpt = exampleStack.getApproximateMatch(layers);
339 approximateMatchOpt) {
340 return *approximateMatchOpt == approximateStack.match;
341 }
342 return false;
343 };
344
345 const auto candidateMatches = [&](const PromotionCandidate& candidate) {
346 ALOGV("[getApproximateMatch] checking against %zx", candidate.hash);
347 return candidate.prediction.getExampleLayerStack().getApproximateMatch(layers) !=
348 std::nullopt;
349 };
350
351 const Prediction* match = nullptr;
352 NonBufferHash hash;
353 if (const auto approximateStackIter =
354 std::find_if(mApproximateStacks.cbegin(), mApproximateStacks.cend(),
355 approximateStackMatches);
356 approximateStackIter != mApproximateStacks.cend()) {
357 match = &mPredictions.at(approximateStackIter->hash);
358 hash = approximateStackIter->hash;
359 } else if (const auto candidateEntry =
360 std::find_if(mCandidates.cbegin(), mCandidates.cend(), candidateMatches);
361 candidateEntry != mCandidates.cend()) {
362 match = &(candidateEntry->prediction);
363 hash = candidateEntry->hash;
364 }
365
366 if (match == nullptr) {
367 return std::nullopt;
368 }
369
370 if (match->getMissCount(Prediction::Type::Approximate) != 0) {
371 ALOGV("[%s] Skipping approximate match for %zx because of prior miss", __func__, hash);
372 return std::nullopt;
373 }
374
375 return hash;
376 }
377
promoteIfCandidate(NonBufferHash predictionHash)378 void Predictor::promoteIfCandidate(NonBufferHash predictionHash) {
379 // Return if the candidate has already been promoted
380 if (mPredictions.count(predictionHash) != 0) {
381 return;
382 }
383
384 ALOGV("[%s] Promoting %zx from candidate to prediction", __func__, predictionHash);
385
386 auto candidateEntry = getCandidateEntryByHash(predictionHash);
387 ALOGE_IF(candidateEntry == mCandidates.end(), "Expected to find candidate");
388
389 mSimilarStacks[candidateEntry->prediction.getPlan()].push_back(predictionHash);
390 mPredictions.emplace(predictionHash, std::move(candidateEntry->prediction));
391 mCandidates.erase(candidateEntry);
392 }
393
recordPredictedResult(PredictedPlan predictedPlan,const std::vector<const LayerState * > & layers,Plan result)394 void Predictor::recordPredictedResult(PredictedPlan predictedPlan,
395 const std::vector<const LayerState*>& layers, Plan result) {
396 Prediction& prediction = getPrediction(predictedPlan.hash);
397 if (prediction.getPlan() != result) {
398 ALOGV("[%s] %s prediction missed, expected %s, found %s", __func__,
399 to_string(predictedPlan.type).c_str(), to_string(prediction.getPlan()).c_str(),
400 to_string(result).c_str());
401 prediction.recordMiss(predictedPlan.type);
402 ++mMissCount;
403 return;
404 }
405
406 switch (predictedPlan.type) {
407 case Prediction::Type::Approximate:
408 ++mApproximateHitCount;
409 break;
410 case Prediction::Type::Exact:
411 ++mExactHitCount;
412 break;
413 default:
414 break;
415 }
416
417 ALOGV("[%s] %s prediction hit", __func__, to_string(predictedPlan.type).c_str());
418 ALOGV("[%s] Plan: %s", __func__, to_string(result).c_str());
419 prediction.recordHit(predictedPlan.type);
420
421 const auto stackMatchesHash = [hash = predictedPlan.hash](const ApproximateStack& stack) {
422 return stack.hash == hash;
423 };
424
425 if (predictedPlan.type == Prediction::Type::Approximate) {
426 // If this approximate match is not already in the list of approximate stacks, add it
427 if (std::find_if(mApproximateStacks.cbegin(), mApproximateStacks.cend(),
428 stackMatchesHash) == mApproximateStacks.cend()) {
429 ALOGV("[%s] Adding approximate match to list", __func__);
430 const auto approximateMatchOpt =
431 prediction.getExampleLayerStack().getApproximateMatch(layers);
432 ALOGE_IF(!approximateMatchOpt, "Expected an approximate match");
433 mApproximateStacks.emplace_back(predictedPlan.hash, *approximateMatchOpt);
434 }
435 }
436
437 promoteIfCandidate(predictedPlan.hash);
438 }
439
findSimilarPrediction(const std::vector<const LayerState * > & layers,Plan result)440 bool Predictor::findSimilarPrediction(const std::vector<const LayerState*>& layers, Plan result) {
441 const auto stacksEntry = mSimilarStacks.find(result);
442 if (stacksEntry == mSimilarStacks.end()) {
443 return false;
444 }
445
446 std::optional<ApproximateStack> bestMatch;
447 const auto& [plan, similarStacks] = *stacksEntry;
448 for (NonBufferHash hash : similarStacks) {
449 const Prediction& prediction = mPredictions.at(hash);
450 auto approximateMatch = prediction.getExampleLayerStack().getApproximateMatch(layers);
451 if (!approximateMatch) {
452 continue;
453 }
454
455 const int differingFieldCount = __builtin_popcount(approximateMatch->differingFields.get());
456 if (!bestMatch ||
457 differingFieldCount < __builtin_popcount(bestMatch->match.differingFields.get())) {
458 bestMatch = {hash, *approximateMatch};
459 }
460 }
461
462 if (!bestMatch) {
463 return false;
464 }
465
466 ALOGV("[%s] Adding %zx to approximate stacks", __func__, bestMatch->hash);
467
468 mApproximateStacks.emplace_back(*bestMatch);
469 return true;
470 }
471
dumpPredictionsByFrequency(std::string & result) const472 void Predictor::dumpPredictionsByFrequency(std::string& result) const {
473 struct HashFrequency {
474 HashFrequency(NonBufferHash hash, size_t totalAttempts)
475 : hash(hash), totalAttempts(totalAttempts) {}
476
477 NonBufferHash hash;
478 size_t totalAttempts;
479 };
480
481 std::vector<HashFrequency> hashFrequencies;
482 for (const auto& [hash, prediction] : mPredictions) {
483 hashFrequencies.emplace_back(hash,
484 prediction.getHitCount(Prediction::Type::Total) +
485 prediction.getMissCount(Prediction::Type::Total));
486 }
487
488 std::sort(hashFrequencies.begin(), hashFrequencies.end(),
489 [](const HashFrequency& lhs, const HashFrequency& rhs) {
490 return lhs.totalAttempts > rhs.totalAttempts;
491 });
492
493 result.append("Predictions:\n");
494 for (const auto& [hash, totalAttempts] : hashFrequencies) {
495 base::StringAppendF(&result, " %016zx ", hash);
496 mPredictions.at(hash).dump(result);
497 result.append("\n");
498 }
499 }
500
501 } // namespace android::compositionengine::impl::planner
502