• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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         }
155     }
156     return result;
157 }
158 
dump(std::string & result) const159 void Prediction::dump(std::string& result) const {
160     result.append(to_string(mPlan));
161     result.append(" [Exact ");
162     mExactStats.dump(result);
163     result.append("] [Approximate ");
164     mApproximateStats.dump(result);
165     result.append("]");
166 }
167 
getPredictedPlan(const std::vector<const LayerState * > & layers,NonBufferHash hash) const168 std::optional<Predictor::PredictedPlan> Predictor::getPredictedPlan(
169         const std::vector<const LayerState*>& layers, NonBufferHash hash) const {
170     // First check for an exact match
171     if (std::optional<Plan> exactMatch = getExactMatch(hash); exactMatch) {
172         ALOGV("[%s] Found an exact match for %zx", __func__, hash);
173         return PredictedPlan{.hash = hash, .plan = *exactMatch, .type = Prediction::Type::Exact};
174     }
175 
176     // If only a hash was passed in for a layer stack with a cached set, don't perform
177     // approximate matches and return early
178     if (layers.empty()) {
179         ALOGV("[%s] Only hash was passed, but no exact match was found", __func__);
180         return std::nullopt;
181     }
182 
183     // Then check for approximate matches
184     if (std::optional<NonBufferHash> approximateMatch = getApproximateMatch(layers);
185         approximateMatch) {
186         ALOGV("[%s] Found an approximate match for %zx", __func__, *approximateMatch);
187         const Prediction& prediction = getPrediction(*approximateMatch);
188         return PredictedPlan{.hash = *approximateMatch,
189                              .plan = prediction.getPlan(),
190                              .type = Prediction::Type::Approximate};
191     }
192 
193     return std::nullopt;
194 }
195 
recordResult(std::optional<PredictedPlan> predictedPlan,NonBufferHash flattenedHash,const std::vector<const LayerState * > & layers,bool hasSkippedLayers,Plan result)196 void Predictor::recordResult(std::optional<PredictedPlan> predictedPlan,
197                              NonBufferHash flattenedHash,
198                              const std::vector<const LayerState*>& layers, bool hasSkippedLayers,
199                              Plan result) {
200     if (predictedPlan) {
201         recordPredictedResult(*predictedPlan, layers, std::move(result));
202         return;
203     }
204 
205     ++mMissCount;
206 
207     if (!hasSkippedLayers && findSimilarPrediction(layers, result)) {
208         return;
209     }
210 
211     ALOGV("[%s] Adding novel candidate %zx", __func__, flattenedHash);
212     mCandidates.emplace_front(flattenedHash, Prediction(layers, result));
213     if (mCandidates.size() > MAX_CANDIDATES) {
214         mCandidates.pop_back();
215     }
216 }
217 
dump(std::string & result) const218 void Predictor::dump(std::string& result) const {
219     result.append("Predictor state:\n");
220 
221     const size_t hitCount = mExactHitCount + mApproximateHitCount;
222     const size_t totalAttempts = hitCount + mMissCount;
223     base::StringAppendF(&result, "Global non-skipped hit rate: %.2f%% (%zd/%zd)\n",
224                         100.0f * hitCount / totalAttempts, hitCount, totalAttempts);
225     base::StringAppendF(&result, "  Exact hits: %zd\n", mExactHitCount);
226     base::StringAppendF(&result, "  Approximate hits: %zd\n", mApproximateHitCount);
227     base::StringAppendF(&result, "  Misses: %zd\n\n", mMissCount);
228 
229     dumpPredictionsByFrequency(result);
230 }
231 
compareLayerStacks(NonBufferHash leftHash,NonBufferHash rightHash,std::string & result) const232 void Predictor::compareLayerStacks(NonBufferHash leftHash, NonBufferHash rightHash,
233                                    std::string& result) const {
234     const auto& [leftPredictionEntry, rightPredictionEntry] =
235             std::make_tuple(mPredictions.find(leftHash), mPredictions.find(rightHash));
236     if (leftPredictionEntry == mPredictions.end()) {
237         base::StringAppendF(&result, "No prediction found for %zx\n", leftHash);
238         return;
239     }
240     if (rightPredictionEntry == mPredictions.end()) {
241         base::StringAppendF(&result, "No prediction found for %zx\n", rightHash);
242         return;
243     }
244 
245     base::StringAppendF(&result,
246                         "Comparing           %-16zx                                %-16zx\n",
247                         leftHash, rightHash);
248 
249     const auto& [leftPrediction, rightPrediction] =
250             std::make_tuple(leftPredictionEntry->second, rightPredictionEntry->second);
251     const auto& [leftStack, rightStack] = std::make_tuple(leftPrediction.getExampleLayerStack(),
252                                                           rightPrediction.getExampleLayerStack());
253     leftStack.compare(rightStack, result);
254 }
255 
describeLayerStack(NonBufferHash hash,std::string & result) const256 void Predictor::describeLayerStack(NonBufferHash hash, std::string& result) const {
257     base::StringAppendF(&result, "Describing %zx:\n\n", hash);
258 
259     if (const auto predictionsEntry = mPredictions.find(hash);
260         predictionsEntry != mPredictions.cend()) {
261         const auto& [hash, prediction] = *predictionsEntry;
262 
263         prediction.getExampleLayerStack().dump(result);
264 
265         result.append("Prediction: ");
266         prediction.dump(result);
267         result.append("\n");
268     } else {
269         result.append("No predictions found\n");
270     }
271 }
272 
listSimilarStacks(Plan plan,std::string & result) const273 void Predictor::listSimilarStacks(Plan plan, std::string& result) const {
274     base::StringAppendF(&result, "Similar stacks for plan %s:\n", to_string(plan).c_str());
275 
276     if (const auto similarStacksEntry = mSimilarStacks.find(plan);
277         similarStacksEntry != mSimilarStacks.end()) {
278         const auto& [_, similarStacks] = *similarStacksEntry;
279         for (NonBufferHash hash : similarStacks) {
280             base::StringAppendF(&result, "\nPrediction hash %zx:\n", hash);
281             const Prediction& prediction = mPredictions.at(hash);
282             prediction.getExampleLayerStack().dumpLayerNames(result);
283         }
284     } else {
285         result.append("No similar stacks found\n");
286     }
287 }
288 
getPrediction(NonBufferHash hash) const289 const Prediction& Predictor::getPrediction(NonBufferHash hash) const {
290     if (const auto predictionEntry = mPredictions.find(hash);
291         predictionEntry != mPredictions.end()) {
292         const auto& [_, prediction] = *predictionEntry;
293         return prediction;
294     } else {
295         const auto candidateEntry = getCandidateEntryByHash(hash);
296         ALOGE_IF(candidateEntry == mCandidates.cend(),
297                  "Hash should have been found in either predictions or candidates");
298         const auto& [_, prediction] = *candidateEntry;
299         return prediction;
300     }
301 }
302 
getPrediction(NonBufferHash hash)303 Prediction& Predictor::getPrediction(NonBufferHash hash) {
304     return const_cast<Prediction&>(const_cast<const Predictor*>(this)->getPrediction(hash));
305 }
306 
getExactMatch(NonBufferHash hash) const307 std::optional<Plan> Predictor::getExactMatch(NonBufferHash hash) const {
308     const Prediction* match = nullptr;
309     if (const auto predictionEntry = mPredictions.find(hash);
310         predictionEntry != mPredictions.end()) {
311         const auto& [hash, prediction] = *predictionEntry;
312         match = &prediction;
313     } else if (const auto candidateEntry = getCandidateEntryByHash(hash);
314                candidateEntry != mCandidates.cend()) {
315         match = &(candidateEntry->prediction);
316     }
317 
318     if (match == nullptr) {
319         return std::nullopt;
320     }
321 
322     if (match->getMissCount(Prediction::Type::Exact) != 0) {
323         ALOGV("[%s] Skipping exact match for %zx because of prior miss", __func__, hash);
324         return std::nullopt;
325     }
326 
327     return match->getPlan();
328 }
329 
getApproximateMatch(const std::vector<const LayerState * > & layers) const330 std::optional<NonBufferHash> Predictor::getApproximateMatch(
331         const std::vector<const LayerState*>& layers) const {
332     const auto approximateStackMatches = [&](const ApproximateStack& approximateStack) {
333         const auto& exampleStack = mPredictions.at(approximateStack.hash).getExampleLayerStack();
334         if (const auto approximateMatchOpt = exampleStack.getApproximateMatch(layers);
335             approximateMatchOpt) {
336             return *approximateMatchOpt == approximateStack.match;
337         }
338         return false;
339     };
340 
341     const auto candidateMatches = [&](const PromotionCandidate& candidate) {
342         ALOGV("[getApproximateMatch] checking against %zx", candidate.hash);
343         return candidate.prediction.getExampleLayerStack().getApproximateMatch(layers) !=
344                 std::nullopt;
345     };
346 
347     const Prediction* match = nullptr;
348     NonBufferHash hash;
349     if (const auto approximateStackIter =
350                 std::find_if(mApproximateStacks.cbegin(), mApproximateStacks.cend(),
351                              approximateStackMatches);
352         approximateStackIter != mApproximateStacks.cend()) {
353         match = &mPredictions.at(approximateStackIter->hash);
354         hash = approximateStackIter->hash;
355     } else if (const auto candidateEntry =
356                        std::find_if(mCandidates.cbegin(), mCandidates.cend(), candidateMatches);
357                candidateEntry != mCandidates.cend()) {
358         match = &(candidateEntry->prediction);
359         hash = candidateEntry->hash;
360     }
361 
362     if (match == nullptr) {
363         return std::nullopt;
364     }
365 
366     if (match->getMissCount(Prediction::Type::Approximate) != 0) {
367         ALOGV("[%s] Skipping approximate match for %zx because of prior miss", __func__, hash);
368         return std::nullopt;
369     }
370 
371     return hash;
372 }
373 
promoteIfCandidate(NonBufferHash predictionHash)374 void Predictor::promoteIfCandidate(NonBufferHash predictionHash) {
375     // Return if the candidate has already been promoted
376     if (mPredictions.count(predictionHash) != 0) {
377         return;
378     }
379 
380     ALOGV("[%s] Promoting %zx from candidate to prediction", __func__, predictionHash);
381 
382     auto candidateEntry = getCandidateEntryByHash(predictionHash);
383     ALOGE_IF(candidateEntry == mCandidates.end(), "Expected to find candidate");
384 
385     mSimilarStacks[candidateEntry->prediction.getPlan()].push_back(predictionHash);
386     mPredictions.emplace(predictionHash, std::move(candidateEntry->prediction));
387     mCandidates.erase(candidateEntry);
388 }
389 
recordPredictedResult(PredictedPlan predictedPlan,const std::vector<const LayerState * > & layers,Plan result)390 void Predictor::recordPredictedResult(PredictedPlan predictedPlan,
391                                       const std::vector<const LayerState*>& layers, Plan result) {
392     Prediction& prediction = getPrediction(predictedPlan.hash);
393     if (prediction.getPlan() != result) {
394         ALOGV("[%s] %s prediction missed, expected %s, found %s", __func__,
395               to_string(predictedPlan.type).c_str(), to_string(prediction.getPlan()).c_str(),
396               to_string(result).c_str());
397         prediction.recordMiss(predictedPlan.type);
398         ++mMissCount;
399         return;
400     }
401 
402     switch (predictedPlan.type) {
403         case Prediction::Type::Approximate:
404             ++mApproximateHitCount;
405             break;
406         case Prediction::Type::Exact:
407             ++mExactHitCount;
408             break;
409         default:
410             break;
411     }
412 
413     ALOGV("[%s] %s prediction hit", __func__, to_string(predictedPlan.type).c_str());
414     ALOGV("[%s] Plan: %s", __func__, to_string(result).c_str());
415     prediction.recordHit(predictedPlan.type);
416 
417     const auto stackMatchesHash = [hash = predictedPlan.hash](const ApproximateStack& stack) {
418         return stack.hash == hash;
419     };
420 
421     if (predictedPlan.type == Prediction::Type::Approximate) {
422         // If this approximate match is not already in the list of approximate stacks, add it
423         if (std::find_if(mApproximateStacks.cbegin(), mApproximateStacks.cend(),
424                          stackMatchesHash) == mApproximateStacks.cend()) {
425             ALOGV("[%s] Adding approximate match to list", __func__);
426             const auto approximateMatchOpt =
427                     prediction.getExampleLayerStack().getApproximateMatch(layers);
428             ALOGE_IF(!approximateMatchOpt, "Expected an approximate match");
429             mApproximateStacks.emplace_back(predictedPlan.hash, *approximateMatchOpt);
430         }
431     }
432 
433     promoteIfCandidate(predictedPlan.hash);
434 }
435 
findSimilarPrediction(const std::vector<const LayerState * > & layers,Plan result)436 bool Predictor::findSimilarPrediction(const std::vector<const LayerState*>& layers, Plan result) {
437     const auto stacksEntry = mSimilarStacks.find(result);
438     if (stacksEntry == mSimilarStacks.end()) {
439         return false;
440     }
441 
442     std::optional<ApproximateStack> bestMatch;
443     const auto& [plan, similarStacks] = *stacksEntry;
444     for (NonBufferHash hash : similarStacks) {
445         const Prediction& prediction = mPredictions.at(hash);
446         auto approximateMatch = prediction.getExampleLayerStack().getApproximateMatch(layers);
447         if (!approximateMatch) {
448             continue;
449         }
450 
451         const int differingFieldCount = __builtin_popcount(approximateMatch->differingFields.get());
452         if (!bestMatch ||
453             differingFieldCount < __builtin_popcount(bestMatch->match.differingFields.get())) {
454             bestMatch = {hash, *approximateMatch};
455         }
456     }
457 
458     if (!bestMatch) {
459         return false;
460     }
461 
462     ALOGV("[%s] Adding %zx to approximate stacks", __func__, bestMatch->hash);
463 
464     mApproximateStacks.emplace_back(*bestMatch);
465     return true;
466 }
467 
dumpPredictionsByFrequency(std::string & result) const468 void Predictor::dumpPredictionsByFrequency(std::string& result) const {
469     struct HashFrequency {
470         HashFrequency(NonBufferHash hash, size_t totalAttempts)
471               : hash(hash), totalAttempts(totalAttempts) {}
472 
473         NonBufferHash hash;
474         size_t totalAttempts;
475     };
476 
477     std::vector<HashFrequency> hashFrequencies;
478     for (const auto& [hash, prediction] : mPredictions) {
479         hashFrequencies.emplace_back(hash,
480                                      prediction.getHitCount(Prediction::Type::Total) +
481                                              prediction.getMissCount(Prediction::Type::Total));
482     }
483 
484     std::sort(hashFrequencies.begin(), hashFrequencies.end(),
485               [](const HashFrequency& lhs, const HashFrequency& rhs) {
486                   return lhs.totalAttempts > rhs.totalAttempts;
487               });
488 
489     result.append("Predictions:\n");
490     for (const auto& [hash, totalAttempts] : hashFrequencies) {
491         base::StringAppendF(&result, "  %016zx ", hash);
492         mPredictions.at(hash).dump(result);
493         result.append("\n");
494     }
495 }
496 
497 } // namespace android::compositionengine::impl::planner
498