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