summaryrefslogtreecommitdiff
path: root/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Predictor.h
diff options
context:
space:
mode:
Diffstat (limited to 'services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Predictor.h')
-rw-r--r--services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Predictor.h321
1 files changed, 321 insertions, 0 deletions
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Predictor.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Predictor.h
new file mode 100644
index 0000000000..fe486d3327
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Predictor.h
@@ -0,0 +1,321 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <compositionengine/impl/planner/LayerState.h>
+
+namespace android::compositionengine::impl::planner {
+
+class LayerStack {
+public:
+ LayerStack(const std::vector<const LayerState*>& layers) : mLayers(copyLayers(layers)) {}
+
+ // Describes an approximate match between two layer stacks
+ struct ApproximateMatch {
+ bool operator==(const ApproximateMatch& other) const {
+ return differingIndex == other.differingIndex &&
+ differingFields == other.differingFields;
+ }
+
+ // The index of the single differing layer between the two stacks.
+ // This implies that only one layer is allowed to differ in an approximate match.
+ size_t differingIndex;
+ // Set of fields that differ for the differing layer in the approximate match.
+ Flags<LayerStateField> differingFields;
+ };
+
+ // Returns an approximate match when comparing this layer stack with the provided list of
+ // layers, for the purposes of scoring how closely the two layer stacks will match composition
+ // strategies.
+ //
+ // If the two layer stacks are identical, then an approximate match is still returned, but the
+ // differing fields will be empty to represent an exact match.
+ //
+ // If the two layer stacks differ by too much, then an empty optional is returned.
+ std::optional<ApproximateMatch> getApproximateMatch(
+ const std::vector<const LayerState*>& other) const;
+
+ void compare(const LayerStack& other, std::string& result) const {
+ if (mLayers.size() != other.mLayers.size()) {
+ base::StringAppendF(&result, "Cannot compare stacks of different sizes (%zd vs. %zd)\n",
+ mLayers.size(), other.mLayers.size());
+ return;
+ }
+
+ for (size_t l = 0; l < mLayers.size(); ++l) {
+ const auto& thisLayer = mLayers[l];
+ const auto& otherLayer = other.mLayers[l];
+ base::StringAppendF(&result, "\n+ - - - - - - - - - Layer %d [%s]\n", thisLayer.getId(),
+ thisLayer.getName().c_str());
+ auto comparisonOpt = thisLayer.compare(otherLayer);
+ base::StringAppendF(&result,
+ " %s + - - - - - - - - - - - - - - - - - - - - - - - "
+ "- Layer %d [%s]\n",
+ comparisonOpt ? " " : "Identical", otherLayer.getId(),
+ otherLayer.getName().c_str());
+ if (comparisonOpt) {
+ result.append(*comparisonOpt);
+ }
+ }
+ }
+
+ void dump(std::string& result) const {
+ for (const LayerState& layer : mLayers) {
+ base::StringAppendF(&result, "+ - - - - - - - - - Layer %d [%s]\n", layer.getId(),
+ layer.getName().c_str());
+ layer.dump(result);
+ }
+ }
+
+ void dumpLayerNames(std::string& result, const std::string& prefix = " ") const {
+ for (const LayerState& layer : mLayers) {
+ result.append(prefix);
+ result.append(layer.getName());
+ result.append("\n");
+ }
+ }
+
+private:
+ std::vector<const LayerState> copyLayers(const std::vector<const LayerState*>& layers) {
+ std::vector<const LayerState> copiedLayers;
+ copiedLayers.reserve(layers.size());
+ std::transform(layers.cbegin(), layers.cend(), std::back_inserter(copiedLayers),
+ [](const LayerState* layerState) { return *layerState; });
+ return copiedLayers;
+ }
+
+ std::vector<const LayerState> mLayers;
+
+ // TODO(b/180976743): Tune kMaxDifferingFields
+ constexpr static int kMaxDifferingFields = 6;
+};
+
+class Plan {
+public:
+ static std::optional<Plan> fromString(const std::string&);
+
+ void reset() { mLayerTypes.clear(); }
+ void addLayerType(hardware::graphics::composer::hal::Composition type) {
+ mLayerTypes.emplace_back(type);
+ }
+
+ friend std::string to_string(const Plan& plan);
+
+ friend bool operator==(const Plan& lhs, const Plan& rhs) {
+ return lhs.mLayerTypes == rhs.mLayerTypes;
+ }
+ friend bool operator!=(const Plan& lhs, const Plan& rhs) { return !(lhs == rhs); }
+
+ friend std::ostream& operator<<(std::ostream& os, const Plan& plan) {
+ return os << to_string(plan);
+ }
+
+private:
+ std::vector<hardware::graphics::composer::hal::Composition> mLayerTypes;
+};
+
+} // namespace android::compositionengine::impl::planner
+
+namespace std {
+template <>
+struct hash<android::compositionengine::impl::planner::Plan> {
+ size_t operator()(const android::compositionengine::impl::planner::Plan& plan) const {
+ return std::hash<std::string>{}(to_string(plan));
+ }
+};
+} // namespace std
+
+namespace android::compositionengine::impl::planner {
+
+class Prediction {
+public:
+ enum class Type {
+ Exact,
+ Approximate,
+ Total,
+ };
+
+ friend std::string to_string(Type type) {
+ using namespace std::string_literals;
+
+ switch (type) {
+ case Type::Exact:
+ return "Exact";
+ case Type::Approximate:
+ return "Approximate";
+ case Type::Total:
+ return "Total";
+ }
+ }
+
+ friend std::ostream& operator<<(std::ostream& os, const Type& type) {
+ return os << to_string(type);
+ }
+
+ Prediction(const std::vector<const LayerState*>& layers, Plan plan)
+ : mExampleLayerStack(layers), mPlan(std::move(plan)) {}
+
+ const LayerStack& getExampleLayerStack() const { return mExampleLayerStack; }
+ const Plan& getPlan() const { return mPlan; }
+
+ size_t getHitCount(Type type) const {
+ if (type == Type::Total) {
+ return getHitCount(Type::Exact) + getHitCount(Type::Approximate);
+ }
+ return getStatsForType(type).hitCount;
+ }
+
+ size_t getMissCount(Type type) const {
+ if (type == Type::Total) {
+ return getMissCount(Type::Exact) + getMissCount(Type::Approximate);
+ }
+ return getStatsForType(type).missCount;
+ }
+
+ void recordHit(Type type) { ++getStatsForType(type).hitCount; }
+
+ void recordMiss(Type type) { ++getStatsForType(type).missCount; }
+
+ void dump(std::string&) const;
+
+private:
+ struct Stats {
+ void dump(std::string& result) const {
+ const size_t totalAttempts = hitCount + missCount;
+ base::StringAppendF(&result, "%.2f%% (%zd/%zd)", 100.0f * hitCount / totalAttempts,
+ hitCount, totalAttempts);
+ }
+
+ size_t hitCount = 0;
+ size_t missCount = 0;
+ };
+
+ const Stats& getStatsForType(Type type) const {
+ return (type == Type::Exact) ? mExactStats : mApproximateStats;
+ }
+
+ Stats& getStatsForType(Type type) {
+ return const_cast<Stats&>(const_cast<const Prediction*>(this)->getStatsForType(type));
+ }
+
+ LayerStack mExampleLayerStack;
+ Plan mPlan;
+
+ Stats mExactStats;
+ Stats mApproximateStats;
+};
+
+class Predictor {
+public:
+ struct PredictedPlan {
+ NonBufferHash hash;
+ Plan plan;
+ Prediction::Type type;
+
+ friend bool operator==(const PredictedPlan& lhs, const PredictedPlan& rhs) {
+ return lhs.hash == rhs.hash && lhs.plan == rhs.plan && lhs.type == rhs.type;
+ }
+ };
+
+ // Retrieves the predicted plan based on a layer stack alongside its hash.
+ //
+ // If the exact layer stack has previously been seen by the predictor, then report the plan used
+ // for that layer stack.
+ //
+ // Otherwise, try to match to the best approximate stack to retireve the most likely plan.
+ std::optional<PredictedPlan> getPredictedPlan(const std::vector<const LayerState*>& layers,
+ NonBufferHash hash) const;
+
+ // Records a comparison between the predicted plan and the resulting plan, alongside the layer
+ // stack we used.
+ //
+ // This method is intended to help with scoring how effective the prediction engine is.
+ void recordResult(std::optional<PredictedPlan> predictedPlan, NonBufferHash flattenedHash,
+ const std::vector<const LayerState*>&, bool hasSkippedLayers, Plan result);
+
+ void dump(std::string&) const;
+
+ void compareLayerStacks(NonBufferHash leftHash, NonBufferHash rightHash, std::string&) const;
+ void describeLayerStack(NonBufferHash, std::string&) const;
+ void listSimilarStacks(Plan, std::string&) const;
+
+private:
+ // Retrieves a prediction from either the main prediction list or from the candidate list
+ const Prediction& getPrediction(NonBufferHash) const;
+ Prediction& getPrediction(NonBufferHash);
+
+ std::optional<Plan> getExactMatch(NonBufferHash) const;
+ std::optional<NonBufferHash> getApproximateMatch(
+ const std::vector<const LayerState*>& layers) const;
+
+ void promoteIfCandidate(NonBufferHash);
+ void recordPredictedResult(PredictedPlan, const std::vector<const LayerState*>& layers,
+ Plan result);
+ bool findSimilarPrediction(const std::vector<const LayerState*>& layers, Plan result);
+
+ void dumpPredictionsByFrequency(std::string&) const;
+
+ struct PromotionCandidate {
+ PromotionCandidate(NonBufferHash hash, Prediction&& prediction)
+ : hash(hash), prediction(std::move(prediction)) {}
+
+ NonBufferHash hash;
+ Prediction prediction;
+ };
+
+ static constexpr const size_t MAX_CANDIDATES = 4;
+ std::deque<PromotionCandidate> mCandidates;
+ decltype(mCandidates)::const_iterator getCandidateEntryByHash(NonBufferHash hash) const {
+ const auto candidateMatches = [&](const PromotionCandidate& candidate) {
+ return candidate.hash == hash;
+ };
+
+ return std::find_if(mCandidates.cbegin(), mCandidates.cend(), candidateMatches);
+ }
+
+ std::unordered_map<NonBufferHash, Prediction> mPredictions;
+ std::unordered_map<Plan, std::vector<NonBufferHash>> mSimilarStacks;
+
+ struct ApproximateStack {
+ ApproximateStack(NonBufferHash hash, LayerStack::ApproximateMatch match)
+ : hash(hash), match(match) {}
+
+ bool operator==(const ApproximateStack& other) const {
+ return hash == other.hash && match == other.match;
+ }
+
+ NonBufferHash hash;
+ LayerStack::ApproximateMatch match;
+ };
+
+ std::vector<ApproximateStack> mApproximateStacks;
+
+ mutable size_t mExactHitCount = 0;
+ mutable size_t mApproximateHitCount = 0;
+ mutable size_t mMissCount = 0;
+};
+
+// Defining PrintTo helps with Google Tests.
+inline void PrintTo(Predictor::PredictedPlan plan, ::std::ostream* os) {
+ *os << "PredictedPlan {";
+ *os << "\n .hash = " << plan.hash;
+ *os << "\n .plan = " << plan.plan;
+ *os << "\n .type = " << plan.type;
+ *os << "\n}";
+}
+
+} // namespace android::compositionengine::impl::planner