From 47437bb9e7395f3261aa8cb96ce2b8fc7db020a3 Mon Sep 17 00:00:00 2001 From: Dan Stoza Date: Fri, 15 Jan 2021 16:21:07 -0800 Subject: SF: Add Planner predictor Adds the Planner predictor, which tries to anticipate the composition strategy that the Composer HAL is going to select based on the layer geometry and historical composition strategies. Bug: 158790260 Test: atest libcompositionengine_test libsurfaceflinger_unittest Change-Id: Icd93e6f8c7437d99108610ac87d649568d13aa77 --- .../surfaceflinger/CompositionEngine/Android.bp | 1 + .../include/compositionengine/impl/Output.h | 1 + .../compositionengine/impl/planner/Planner.h | 15 + .../compositionengine/impl/planner/Predictor.h | 278 ++++++++++++ .../CompositionEngine/src/Output.cpp | 4 + .../CompositionEngine/src/planner/Planner.cpp | 150 ++++++- .../CompositionEngine/src/planner/Predictor.cpp | 479 +++++++++++++++++++++ .../CompositionEngine/tests/OutputTest.cpp | 3 + 8 files changed, 929 insertions(+), 2 deletions(-) create mode 100644 services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Predictor.h create mode 100644 services/surfaceflinger/CompositionEngine/src/planner/Predictor.cpp diff --git a/services/surfaceflinger/CompositionEngine/Android.bp b/services/surfaceflinger/CompositionEngine/Android.bp index ade82f688f..e2f232429b 100644 --- a/services/surfaceflinger/CompositionEngine/Android.bp +++ b/services/surfaceflinger/CompositionEngine/Android.bp @@ -54,6 +54,7 @@ cc_library { srcs: [ "src/planner/LayerState.cpp", "src/planner/Planner.cpp", + "src/planner/Predictor.cpp", "src/ClientCompositionRequestCache.cpp", "src/CompositionEngine.cpp", "src/Display.cpp", diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h index ae35fb0489..044ddd8447 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h @@ -100,6 +100,7 @@ public: const ReleasedLayers& getReleasedLayersForTest() const; void setDisplayColorProfileForTest(std::unique_ptr); void setRenderSurfaceForTest(std::unique_ptr); + bool plannerEnabled() const { return mPlanner != nullptr; } protected: std::unique_ptr createOutputLayer(const sp&) const; diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Planner.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Planner.h index 53eca01aa4..ad53d27579 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Planner.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Planner.h @@ -18,9 +18,11 @@ #include #include +#include #include #include +#include #include #include @@ -39,10 +41,23 @@ public: void plan( compositionengine::Output::OutputLayersEnumerator&& layers); + // Updates the Planner with the current set of layers after a composition strategy is + // determined. + void reportFinalPlan( + compositionengine::Output::OutputLayersEnumerator&& layers); + void dump(const Vector& args, std::string&); private: + void dumpUsage(std::string&) const; + std::unordered_map mPreviousLayers; + + std::vector mCurrentLayers; + + Predictor mPredictor; + + std::optional mPredictedPlan; }; } // namespace compositionengine::impl::planner 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..38c73f79c4 --- /dev/null +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Predictor.h @@ -0,0 +1,278 @@ +/* + * 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 + +namespace android::compositionengine::impl::planner { + +class LayerStack { +public: + LayerStack(const std::vector& layers) : mLayers(copyLayers(layers)) {} + + struct ApproximateMatch { + bool operator==(const ApproximateMatch& other) const { + return differingIndex == other.differingIndex && + differingFields == other.differingFields; + } + + size_t differingIndex; + Flags differingFields; + }; + + std::optional getApproximateMatch( + const std::vector& 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 copyLayers(const std::vector& layers) { + std::vector copiedLayers; + copiedLayers.reserve(layers.size()); + std::transform(layers.cbegin(), layers.cend(), std::back_inserter(copiedLayers), + [](const LayerState* layerState) { return *layerState; }); + return copiedLayers; + } + + std::vector mLayers; + + // TODO(b/180976743): Tune kMaxDifferingFields + constexpr static int kMaxDifferingFields = 6; +}; + +class Plan { +public: + static std::optional 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); } + +private: + std::vector mLayerTypes; +}; + +} // namespace android::compositionengine::impl::planner + +namespace std { +template <> +struct hash { + size_t operator()(const android::compositionengine::impl::planner::Plan& plan) const { + return std::hash{}(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"; + } + } + + Prediction(const std::vector& 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(const_cast(this)->getStatsForType(type)); + } + + LayerStack mExampleLayerStack; + Plan mPlan; + + Stats mExactStats; + Stats mApproximateStats; +}; + +class Predictor { +public: + struct PredictedPlan { + NonBufferHash hash; + Plan plan; + Prediction::Type type; + }; + + std::optional getPredictedPlan(const std::vector&, + NonBufferHash) const; + + void recordResult(std::optional predictedPlan, + const std::vector&, 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 getExactMatch(NonBufferHash) const; + std::optional getApproximateMatch( + const std::vector& layers) const; + + void promoteIfCandidate(NonBufferHash); + void recordPredictedResult(PredictedPlan, const std::vector& layers, + Plan result); + bool findSimilarPrediction(const std::vector& 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 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 mPredictions; + std::unordered_map> 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 mApproximateStacks; + + mutable size_t mExactHitCount = 0; + mutable size_t mApproximateHitCount = 0; + mutable size_t mMissCount = 0; +}; + +} // namespace android::compositionengine::impl::planner diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp index 709d7c9f48..57b7481b81 100644 --- a/services/surfaceflinger/CompositionEngine/src/Output.cpp +++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp @@ -873,6 +873,10 @@ void Output::prepareFrame() { chooseCompositionStrategy(); + if (mPlanner) { + mPlanner->reportFinalPlan(getOutputLayersOrderedByZ()); + } + mRenderSurface->prepareFrame(outputState.usesClientComposition, outputState.usesDeviceComposition); } diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp index 30f48922b6..619d008e41 100644 --- a/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp +++ b/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp @@ -32,6 +32,7 @@ void Planner::plan( std::inserter(removedLayers, removedLayers.begin()), [](const auto& layer) { return layer.first; }); + std::vector currentLayerIds; for (auto layer : layers) { LayerId id = layer->getLayerFE().getSequence(); if (const auto layerEntry = mPreviousLayers.find(id); layerEntry != mPreviousLayers.end()) { @@ -54,6 +55,8 @@ void Planner::plan( mPreviousLayers.emplace(std::make_pair(id, std::move(state))); } + currentLayerIds.emplace_back(id); + if (const auto found = removedLayers.find(id); found != removedLayers.end()) { removedLayers.erase(found); } @@ -67,10 +70,153 @@ void Planner::plan( mPreviousLayers.erase(removedLayer); } } + + mCurrentLayers.clear(); + mCurrentLayers.reserve(currentLayerIds.size()); + std::transform(currentLayerIds.cbegin(), currentLayerIds.cend(), + std::back_inserter(mCurrentLayers), + [this](LayerId id) { return &mPreviousLayers.at(id); }); + + mPredictedPlan = mPredictor.getPredictedPlan(mCurrentLayers, getNonBufferHash(mCurrentLayers)); + if (mPredictedPlan) { + ALOGV("[%s] Predicting plan %s", __func__, to_string(mPredictedPlan->plan).c_str()); + } else { + ALOGV("[%s] No prediction found\n", __func__); + } +} + +void Planner::reportFinalPlan( + compositionengine::Output::OutputLayersEnumerator&& layers) { + Plan finalPlan; + for (auto layer : layers) { + const bool forcedOrRequestedClient = + layer->getState().forceClientComposition || layer->requiresClientComposition(); + + finalPlan.addLayerType( + forcedOrRequestedClient + ? hardware::graphics::composer::hal::Composition::CLIENT + : layer->getLayerFE().getCompositionState()->compositionType); + } + + mPredictor.recordResult(mPredictedPlan, mCurrentLayers, finalPlan); +} + +void Planner::dump(const Vector& args, std::string& result) { + if (args.size() > 1) { + const String8 command(args[1]); + if (command == "--compare" || command == "-c") { + if (args.size() < 4) { + base::StringAppendF(&result, + "Expected two layer stack hashes, e.g. '--planner %s " + " '\n", + command.string()); + return; + } + if (args.size() > 4) { + base::StringAppendF(&result, + "Too many arguments found, expected '--planner %s " + "'\n", + command.string()); + return; + } + + const String8 leftHashString(args[2]); + size_t leftHash = 0; + int fieldsRead = sscanf(leftHashString.string(), "%zx", &leftHash); + if (fieldsRead != 1) { + base::StringAppendF(&result, "Failed to parse %s as a size_t\n", + leftHashString.string()); + return; + } + + const String8 rightHashString(args[3]); + size_t rightHash = 0; + fieldsRead = sscanf(rightHashString.string(), "%zx", &rightHash); + if (fieldsRead != 1) { + base::StringAppendF(&result, "Failed to parse %s as a size_t\n", + rightHashString.string()); + return; + } + + mPredictor.compareLayerStacks(leftHash, rightHash, result); + } else if (command == "--describe" || command == "-d") { + if (args.size() < 3) { + base::StringAppendF(&result, + "Expected a layer stack hash, e.g. '--planner %s '\n", + command.string()); + return; + } + if (args.size() > 3) { + base::StringAppendF(&result, + "Too many arguments found, expected '--planner %s '\n", + command.string()); + return; + } + + const String8 hashString(args[2]); + size_t hash = 0; + const int fieldsRead = sscanf(hashString.string(), "%zx", &hash); + if (fieldsRead != 1) { + base::StringAppendF(&result, "Failed to parse %s as a size_t\n", + hashString.string()); + return; + } + + mPredictor.describeLayerStack(hash, result); + } else if (command == "--help" || command == "-h") { + dumpUsage(result); + } else if (command == "--similar" || command == "-s") { + if (args.size() < 3) { + base::StringAppendF(&result, "Expected a plan string, e.g. '--planner %s '\n", + command.string()); + return; + } + if (args.size() > 3) { + base::StringAppendF(&result, + "Too many arguments found, expected '--planner %s '\n", + command.string()); + return; + } + + const String8 planString(args[2]); + std::optional plan = Plan::fromString(std::string(planString.string())); + if (!plan) { + base::StringAppendF(&result, "Failed to parse %s as a Plan\n", planString.string()); + return; + } + + mPredictor.listSimilarStacks(*plan, result); + } else { + base::StringAppendF(&result, "Unknown command '%s'\n\n", command.string()); + dumpUsage(result); + } + return; + } + + // If there are no specific commands, dump the usual state + mPredictor.dump(result); } -void Planner::dump(const Vector& /* args */, std::string& result) { - result.append("Dumping Planner state"); +void Planner::dumpUsage(std::string& result) const { + result.append("Planner command line interface usage\n"); + result.append(" dumpsys SurfaceFlinger --planner [arguments]\n\n"); + + result.append("If run without a command, dumps current Planner state\n\n"); + + result.append("Commands:\n"); + + result.append("[--compare|-c] \n"); + result.append(" Compares the predictions and by showing differences" + " in their example layer stacks\n"); + + result.append("[--describe|-d] \n"); + result.append(" Prints the example layer stack and prediction statistics for \n"); + + result.append("[--help|-h]\n"); + result.append(" Shows this message\n"); + + result.append("[--similar|-s] \n"); + result.append(" Prints the example layer names for similar stacks matching \n"); } } // namespace android::compositionengine::impl::planner diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Predictor.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Predictor.cpp new file mode 100644 index 0000000000..28be9db8cf --- /dev/null +++ b/services/surfaceflinger/CompositionEngine/src/planner/Predictor.cpp @@ -0,0 +1,479 @@ +/* + * 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. + */ + +// #define LOG_NDEBUG 0 + +#undef LOG_TAG +#define LOG_TAG "Planner" + +#include + +namespace android::compositionengine::impl::planner { + +std::optional LayerStack::getApproximateMatch( + const std::vector& other) const { + // Differing numbers of layers are never an approximate match + if (mLayers.size() != other.size()) { + return std::nullopt; + } + + std::optional approximateMatch = {}; + for (size_t i = 0; i < mLayers.size(); ++i) { + // Skip identical layers + if (mLayers[i].getHash(LayerStateField::Buffer) == + other[i]->getHash(LayerStateField::Buffer)) { + continue; + } + + // Skip layers where both are client-composited, since that doesn't change the + // composition plan + if (mLayers[i].getCompositionType() == hal::Composition::CLIENT && + other[i]->getCompositionType() == hal::Composition::CLIENT) { + continue; + } + + // If layers differ in composition type, their stacks are too different + if (mLayers[i].getCompositionType() != other[i]->getCompositionType()) { + return std::nullopt; + } + + // If layers are not identical, but we already have a prior approximate match, + // the LayerStacks differ by too much, so return nothing + if (approximateMatch) { + return std::nullopt; + } + + Flags differingFields = + mLayers[i].getDifferingFields(*other[i], LayerStateField::Buffer); + + // If we don't find an approximate match on this layer, then the LayerStacks differ + // by too much, so return nothing + const int differingFieldCount = __builtin_popcount(differingFields.get()); + if (differingFieldCount <= kMaxDifferingFields) { + approximateMatch = ApproximateMatch{ + .differingIndex = i, + .differingFields = differingFields, + }; + } else { + return std::nullopt; + } + } + + // If we make it through the layer-by-layer comparison without an approximate match, + // it means that all layers were either identical or had client-composited layers in common, + // which don't affect the composition strategy, so return a successful result with + // no differences. + return ApproximateMatch{ + .differingIndex = 0, + .differingFields = {}, + }; +} + +std::optional Plan::fromString(const std::string& string) { + Plan plan; + for (char c : string) { + switch (c) { + case 'C': + plan.addLayerType(hal::Composition::CLIENT); + continue; + case 'U': + plan.addLayerType(hal::Composition::CURSOR); + continue; + case 'D': + plan.addLayerType(hal::Composition::DEVICE); + continue; + case 'I': + plan.addLayerType(hal::Composition::INVALID); + continue; + case 'B': + plan.addLayerType(hal::Composition::SIDEBAND); + continue; + case 'S': + plan.addLayerType(hal::Composition::SOLID_COLOR); + continue; + default: + return std::nullopt; + } + } + return plan; +} + +std::string to_string(const Plan& plan) { + std::string result; + for (auto type : plan.mLayerTypes) { + switch (type) { + case hal::Composition::CLIENT: + result.append("C"); + break; + case hal::Composition::CURSOR: + result.append("U"); + break; + case hal::Composition::DEVICE: + result.append("D"); + break; + case hal::Composition::INVALID: + result.append("I"); + break; + case hal::Composition::SIDEBAND: + result.append("B"); + break; + case hal::Composition::SOLID_COLOR: + result.append("S"); + break; + } + } + return result; +} + +void Prediction::dump(std::string& result) const { + result.append(to_string(mPlan)); + result.append(" [Exact "); + mExactStats.dump(result); + result.append("] [Approximate "); + mApproximateStats.dump(result); + result.append("]"); +} + +std::optional Predictor::getPredictedPlan( + const std::vector& layers, NonBufferHash hash) const { + // First check for an exact match + if (std::optional exactMatch = getExactMatch(hash); exactMatch) { + ALOGV("[%s] Found an exact match for %zx", __func__, hash); + return PredictedPlan{.hash = hash, .plan = *exactMatch, .type = Prediction::Type::Exact}; + } + + // If only a hash was passed in for a layer stack with a cached set, don't perform + // approximate matches and return early + if (layers.empty()) { + ALOGV("[%s] Only hash was passed, but no exact match was found", __func__); + return std::nullopt; + } + + // Then check for approximate matches + if (std::optional approximateMatch = getApproximateMatch(layers); + approximateMatch) { + ALOGV("[%s] Found an approximate match for %zx", __func__, *approximateMatch); + const Prediction& prediction = getPrediction(*approximateMatch); + return PredictedPlan{.hash = *approximateMatch, + .plan = prediction.getPlan(), + .type = Prediction::Type::Approximate}; + } + + return std::nullopt; +} + +void Predictor::recordResult(std::optional predictedPlan, + const std::vector& layers, Plan result) { + if (predictedPlan) { + recordPredictedResult(*predictedPlan, layers, std::move(result)); + return; + } + + ++mMissCount; + + if (findSimilarPrediction(layers, result)) { + return; + } + + const NonBufferHash hash = getNonBufferHash(layers); + + ALOGV("[%s] Adding novel candidate %zx", __func__, hash); + mCandidates.emplace_front(hash, Prediction(layers, result)); + if (mCandidates.size() > MAX_CANDIDATES) { + mCandidates.pop_back(); + } +} + +void Predictor::dump(std::string& result) const { + result.append("Predictor state:\n"); + + const size_t hitCount = mExactHitCount + mApproximateHitCount; + const size_t totalAttempts = hitCount + mMissCount; + base::StringAppendF(&result, "Global non-skipped hit rate: %.2f%% (%zd/%zd)\n", + 100.0f * hitCount / totalAttempts, hitCount, totalAttempts); + base::StringAppendF(&result, " Exact hits: %zd\n", mExactHitCount); + base::StringAppendF(&result, " Approximate hits: %zd\n", mApproximateHitCount); + base::StringAppendF(&result, " Misses: %zd\n\n", mMissCount); + + dumpPredictionsByFrequency(result); +} + +void Predictor::compareLayerStacks(NonBufferHash leftHash, NonBufferHash rightHash, + std::string& result) const { + const auto& [leftPredictionEntry, rightPredictionEntry] = + std::make_tuple(mPredictions.find(leftHash), mPredictions.find(rightHash)); + if (leftPredictionEntry == mPredictions.end()) { + base::StringAppendF(&result, "No prediction found for %zx\n", leftHash); + return; + } + if (rightPredictionEntry == mPredictions.end()) { + base::StringAppendF(&result, "No prediction found for %zx\n", rightHash); + return; + } + + base::StringAppendF(&result, + "Comparing %-16zx %-16zx\n", + leftHash, rightHash); + + const auto& [leftPrediction, rightPrediction] = + std::make_tuple(leftPredictionEntry->second, rightPredictionEntry->second); + const auto& [leftStack, rightStack] = std::make_tuple(leftPrediction.getExampleLayerStack(), + rightPrediction.getExampleLayerStack()); + leftStack.compare(rightStack, result); +} + +void Predictor::describeLayerStack(NonBufferHash hash, std::string& result) const { + base::StringAppendF(&result, "Describing %zx:\n\n", hash); + + if (const auto predictionsEntry = mPredictions.find(hash); + predictionsEntry != mPredictions.cend()) { + const auto& [hash, prediction] = *predictionsEntry; + + prediction.getExampleLayerStack().dump(result); + + result.append("Prediction: "); + prediction.dump(result); + result.append("\n"); + } else { + result.append("No predictions found\n"); + } +} + +void Predictor::listSimilarStacks(Plan plan, std::string& result) const { + base::StringAppendF(&result, "Similar stacks for plan %s:\n", to_string(plan).c_str()); + + if (const auto similarStacksEntry = mSimilarStacks.find(plan); + similarStacksEntry != mSimilarStacks.end()) { + const auto& [_, similarStacks] = *similarStacksEntry; + for (NonBufferHash hash : similarStacks) { + base::StringAppendF(&result, "\nPrediction hash %zx:\n", hash); + const Prediction& prediction = mPredictions.at(hash); + prediction.getExampleLayerStack().dumpLayerNames(result); + } + } else { + result.append("No similar stacks found\n"); + } +} + +const Prediction& Predictor::getPrediction(NonBufferHash hash) const { + if (const auto predictionEntry = mPredictions.find(hash); + predictionEntry != mPredictions.end()) { + const auto& [_, prediction] = *predictionEntry; + return prediction; + } else { + const auto candidateEntry = getCandidateEntryByHash(hash); + ALOGE_IF(candidateEntry == mCandidates.cend(), + "Hash should have been found in either predictions or candidates"); + const auto& [_, prediction] = *candidateEntry; + return prediction; + } +} + +Prediction& Predictor::getPrediction(NonBufferHash hash) { + return const_cast(const_cast(this)->getPrediction(hash)); +} + +std::optional Predictor::getExactMatch(NonBufferHash hash) const { + const Prediction* match = nullptr; + if (const auto predictionEntry = mPredictions.find(hash); + predictionEntry != mPredictions.end()) { + const auto& [hash, prediction] = *predictionEntry; + match = &prediction; + } else if (const auto candidateEntry = getCandidateEntryByHash(hash); + candidateEntry != mCandidates.cend()) { + match = &(candidateEntry->prediction); + } + + if (match == nullptr) { + return std::nullopt; + } + + if (match->getMissCount(Prediction::Type::Exact) != 0) { + ALOGV("[%s] Skipping exact match for %zx because of prior miss", __func__, hash); + return std::nullopt; + } + + return match->getPlan(); +} + +std::optional Predictor::getApproximateMatch( + const std::vector& layers) const { + const auto approximateStackMatches = [&](const ApproximateStack& approximateStack) { + const auto& exampleStack = mPredictions.at(approximateStack.hash).getExampleLayerStack(); + if (const auto approximateMatchOpt = exampleStack.getApproximateMatch(layers); + approximateMatchOpt) { + return *approximateMatchOpt == approximateStack.match; + } + return false; + }; + + const auto candidateMatches = [&](const PromotionCandidate& candidate) { + ALOGV("[getApproximateMatch] checking against %zx", candidate.hash); + return candidate.prediction.getExampleLayerStack().getApproximateMatch(layers) != + std::nullopt; + }; + + const Prediction* match = nullptr; + NonBufferHash hash; + if (const auto approximateStackIter = + std::find_if(mApproximateStacks.cbegin(), mApproximateStacks.cend(), + approximateStackMatches); + approximateStackIter != mApproximateStacks.cend()) { + match = &mPredictions.at(approximateStackIter->hash); + hash = approximateStackIter->hash; + } else if (const auto candidateEntry = + std::find_if(mCandidates.cbegin(), mCandidates.cend(), candidateMatches); + candidateEntry != mCandidates.cend()) { + match = &(candidateEntry->prediction); + hash = candidateEntry->hash; + } + + if (match == nullptr) { + return std::nullopt; + } + + if (match->getMissCount(Prediction::Type::Approximate) != 0) { + ALOGV("[%s] Skipping approximate match for %zx because of prior miss", __func__, hash); + return std::nullopt; + } + + return hash; +} + +void Predictor::promoteIfCandidate(NonBufferHash predictionHash) { + // Return if the candidate has already been promoted + if (mPredictions.count(predictionHash) != 0) { + return; + } + + ALOGV("[%s] Promoting %zx from candidate to prediction", __func__, predictionHash); + + auto candidateEntry = getCandidateEntryByHash(predictionHash); + ALOGE_IF(candidateEntry == mCandidates.end(), "Expected to find candidate"); + + mSimilarStacks[candidateEntry->prediction.getPlan()].push_back(predictionHash); + mPredictions.emplace(predictionHash, std::move(candidateEntry->prediction)); + mCandidates.erase(candidateEntry); +} + +void Predictor::recordPredictedResult(PredictedPlan predictedPlan, + const std::vector& layers, Plan result) { + Prediction& prediction = getPrediction(predictedPlan.hash); + if (prediction.getPlan() != result) { + ALOGV("[%s] %s prediction missed, expected %s, found %s", __func__, + to_string(predictedPlan.type).c_str(), to_string(prediction.getPlan()).c_str(), + to_string(result).c_str()); + prediction.recordMiss(predictedPlan.type); + ++mMissCount; + return; + } + + switch (predictedPlan.type) { + case Prediction::Type::Approximate: + ++mApproximateHitCount; + break; + case Prediction::Type::Exact: + ++mExactHitCount; + break; + default: + break; + } + + ALOGV("[%s] %s prediction hit", __func__, to_string(predictedPlan.type).c_str()); + ALOGV("[%s] Plan: %s", __func__, to_string(result).c_str()); + prediction.recordHit(predictedPlan.type); + + const auto stackMatchesHash = [hash = predictedPlan.hash](const ApproximateStack& stack) { + return stack.hash == hash; + }; + + if (predictedPlan.type == Prediction::Type::Approximate) { + // If this approximate match is not already in the list of approximate stacks, add it + if (std::find_if(mApproximateStacks.cbegin(), mApproximateStacks.cend(), + stackMatchesHash) == mApproximateStacks.cend()) { + ALOGV("[%s] Adding approximate match to list", __func__); + const auto approximateMatchOpt = + prediction.getExampleLayerStack().getApproximateMatch(layers); + ALOGE_IF(!approximateMatchOpt, "Expected an approximate match"); + mApproximateStacks.emplace_back(predictedPlan.hash, *approximateMatchOpt); + } + } + + promoteIfCandidate(predictedPlan.hash); +} + +bool Predictor::findSimilarPrediction(const std::vector& layers, Plan result) { + const auto stacksEntry = mSimilarStacks.find(result); + if (stacksEntry == mSimilarStacks.end()) { + return false; + } + + std::optional bestMatch; + const auto& [plan, similarStacks] = *stacksEntry; + for (NonBufferHash hash : similarStacks) { + const Prediction& prediction = mPredictions.at(hash); + auto approximateMatch = prediction.getExampleLayerStack().getApproximateMatch(layers); + if (!approximateMatch) { + continue; + } + + const int differingFieldCount = __builtin_popcount(approximateMatch->differingFields.get()); + if (!bestMatch || + differingFieldCount < __builtin_popcount(bestMatch->match.differingFields.get())) { + bestMatch = {hash, *approximateMatch}; + } + } + + if (!bestMatch) { + return false; + } + + ALOGV("[%s] Adding %zx to approximate stacks", __func__, bestMatch->hash); + + mApproximateStacks.emplace_back(*bestMatch); + return true; +} + +void Predictor::dumpPredictionsByFrequency(std::string& result) const { + struct HashFrequency { + HashFrequency(NonBufferHash hash, size_t totalAttempts) + : hash(hash), totalAttempts(totalAttempts) {} + + NonBufferHash hash; + size_t totalAttempts; + }; + + std::vector hashFrequencies; + for (const auto& [hash, prediction] : mPredictions) { + hashFrequencies.emplace_back(hash, + prediction.getHitCount(Prediction::Type::Total) + + prediction.getMissCount(Prediction::Type::Total)); + } + + std::sort(hashFrequencies.begin(), hashFrequencies.end(), + [](const HashFrequency& lhs, const HashFrequency& rhs) { + return lhs.totalAttempts > rhs.totalAttempts; + }); + + result.append("Predictions:\n"); + for (const auto& [hash, totalAttempts] : hashFrequencies) { + base::StringAppendF(&result, " %016zx ", hash); + mPredictions.at(hash).dump(result); + result.append("\n"); + } +} + +} // namespace android::compositionengine::impl::planner \ No newline at end of file diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp index f654c2fd07..8dcace6b6c 100644 --- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp @@ -891,6 +891,9 @@ TEST_F(OutputPrepareFrameTest, delegatesToChooseCompositionStrategyAndRenderSurf mOutput.editState().usesDeviceComposition = true; EXPECT_CALL(mOutput, chooseCompositionStrategy()).Times(1); + if (mOutput.plannerEnabled()) { + EXPECT_CALL(mOutput, getOutputLayerCount()).WillOnce(Return(0u)); + } EXPECT_CALL(*mRenderSurface, prepareFrame(false, true)); mOutput.prepareFrame(); -- cgit v1.2.3-59-g8ed1b From 6166c312bc1e349623a493c683034dd1a2ac4869 Mon Sep 17 00:00:00 2001 From: Dan Stoza Date: Fri, 15 Jan 2021 16:34:05 -0800 Subject: SF: Add Planner flattener Adds the Planner flattener, which detects when layers are inactive and flattens them using GPU composition in order to reduce the workload on the display processor. Bug: 158790260 Test: atest libcompositionengine_test libsurfaceflinger_unittest Change-Id: Idc5c2927c8af6fe85653404a7d94c9e68ffc329b --- .../surfaceflinger/CompositionEngine/Android.bp | 2 + .../include/compositionengine/Output.h | 1 + .../include/compositionengine/OutputLayer.h | 12 +- .../include/compositionengine/impl/Output.h | 1 + .../include/compositionengine/impl/OutputLayer.h | 7 +- .../impl/OutputLayerCompositionState.h | 7 + .../compositionengine/impl/planner/CachedSet.h | 115 +++++++ .../compositionengine/impl/planner/Flattener.h | 86 +++++ .../compositionengine/impl/planner/Planner.h | 17 + .../compositionengine/impl/planner/Predictor.h | 4 +- .../include/compositionengine/mock/Output.h | 1 + .../include/compositionengine/mock/OutputLayer.h | 3 +- .../CompositionEngine/src/Output.cpp | 48 ++- .../CompositionEngine/src/OutputLayer.cpp | 88 +++-- .../CompositionEngine/src/planner/CachedSet.cpp | 243 ++++++++++++++ .../CompositionEngine/src/planner/Flattener.cpp | 355 +++++++++++++++++++++ .../CompositionEngine/src/planner/Planner.cpp | 51 ++- .../CompositionEngine/src/planner/Predictor.cpp | 14 +- .../CompositionEngine/tests/OutputLayerTest.cpp | 26 +- .../CompositionEngine/tests/OutputTest.cpp | 50 ++- 20 files changed, 1048 insertions(+), 83 deletions(-) create mode 100644 services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h create mode 100644 services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h create mode 100644 services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp create mode 100644 services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp diff --git a/services/surfaceflinger/CompositionEngine/Android.bp b/services/surfaceflinger/CompositionEngine/Android.bp index e2f232429b..f9e5b9a801 100644 --- a/services/surfaceflinger/CompositionEngine/Android.bp +++ b/services/surfaceflinger/CompositionEngine/Android.bp @@ -52,6 +52,8 @@ cc_library { name: "libcompositionengine", defaults: ["libcompositionengine_defaults"], srcs: [ + "src/planner/CachedSet.cpp", + "src/planner/Flattener.cpp", "src/planner/LayerState.cpp", "src/planner/Planner.cpp", "src/planner/Predictor.cpp", diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h index a8ecb62163..497621397c 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h @@ -280,6 +280,7 @@ protected: virtual std::optional composeSurfaces( const Region&, const compositionengine::CompositionRefreshArgs& refreshArgs) = 0; virtual void postFramebuffer() = 0; + virtual void renderCachedSets() = 0; virtual void chooseCompositionStrategy() = 0; virtual bool getSkipColorTransform() const = 0; virtual FrameFences presentAndGetFrameFences() = 0; diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h index fb19216cc9..3a843273b9 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h @@ -30,6 +30,8 @@ #include "DisplayHardware/ComposerHal.h" #include "DisplayHardware/DisplayIdentification.h" +#include "LayerFE.h" + // TODO(b/129481165): remove the #pragma below and fix conversion issues #pragma clang diagnostic pop // ignored "-Wconversion -Wextra" @@ -43,7 +45,6 @@ namespace compositionengine { class CompositionEngine; class Output; -class LayerFE; namespace impl { struct OutputLayerCompositionState; @@ -88,8 +89,9 @@ public: // Writes the geometry state to the HWC, or does nothing if this layer does // not use the HWC. If includeGeometry is false, the geometry state can be - // skipped. - virtual void writeStateToHWC(bool includeGeometry) = 0; + // skipped. If skipLayer is true, then the alpha of the layer is forced to + // 0 so that HWC will ignore it. + virtual void writeStateToHWC(bool includeGeometry, bool skipLayer) = 0; // Updates the cursor position with the HWC virtual void writeCursorPositionToHWC() const = 0; @@ -115,6 +117,10 @@ public: // Returns true if the composition settings scale pixels virtual bool needsFiltering() const = 0; + // Returns a composition list to be used by RenderEngine if the layer has been overridden + // during the composition process + virtual std::vector getOverrideCompositionList() const = 0; + // Debugging virtual void dump(std::string& result) const = 0; }; diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h index 044ddd8447..eeb20fc1d1 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h @@ -94,6 +94,7 @@ public: std::optional composeSurfaces( const Region&, const compositionengine::CompositionRefreshArgs& refreshArgs) override; void postFramebuffer() override; + void renderCachedSets() override; void cacheClientCompositionRequests(uint32_t) override; // Testing diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h index 8cb5ae8b8e..f113c34ba3 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -41,7 +42,7 @@ public: void updateCompositionState(bool includeGeometry, bool forceClientComposition, ui::Transform::RotationFlags) override; - void writeStateToHWC(bool) override; + void writeStateToHWC(bool includeGeometry, bool skipLayer) override; void writeCursorPositionToHWC() const override; HWC2::Layer* getHwcLayer() const override; @@ -51,6 +52,7 @@ public: void prepareForDeviceLayerRequests() override; void applyDeviceLayerRequest(Hwc2::IComposerClient::LayerRequest request) override; bool needsFiltering() const override; + std::vector getOverrideCompositionList() const override; void dump(std::string&) const override; @@ -66,7 +68,8 @@ protected: private: Rect calculateInitialCrop() const; void writeOutputDependentGeometryStateToHWC(HWC2::Layer*, Hwc2::IComposerClient::Composition); - void writeOutputIndependentGeometryStateToHWC(HWC2::Layer*, const LayerFECompositionState&); + void writeOutputIndependentGeometryStateToHWC(HWC2::Layer*, const LayerFECompositionState&, + bool skipLayer); void writeOutputDependentPerFrameStateToHWC(HWC2::Layer*); void writeOutputIndependentPerFrameStateToHWC(HWC2::Layer*, const LayerFECompositionState&); void writeSolidColorStateToHWC(HWC2::Layer*, const LayerFECompositionState&); diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h index 9a118d3f3a..5f834beb14 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h @@ -84,6 +84,13 @@ struct OutputLayerCompositionState { // The Z order index of this layer on this output uint32_t z{0}; + // Overrides the buffer, acquire fence, and display frame stored in LayerFECompositionState + struct { + sp buffer = nullptr; + sp acquireFence = nullptr; + Rect displayFrame = {}; + } overrideInfo; + /* * HWC state */ diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h new file mode 100644 index 0000000000..00424b21ac --- /dev/null +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h @@ -0,0 +1,115 @@ +/* + * 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 + +#include + +namespace android { + +namespace renderengine { +class RenderEngine; +} // namespace renderengine + +namespace compositionengine::impl::planner { + +std::string durationString(std::chrono::milliseconds duration); + +class LayerState; + +class CachedSet { +public: + class Layer { + public: + Layer(const LayerState*, std::chrono::steady_clock::time_point lastUpdate); + + const LayerState* getState() const { return mState; } + const std::string& getName() const { return mState->getName(); } + Rect getDisplayFrame() const { return mState->getDisplayFrame(); } + const sp& getBuffer() const { return mState->getBuffer(); } + int64_t getFramesSinceBufferUpdate() const { return mState->getFramesSinceBufferUpdate(); } + NonBufferHash getHash() const { return mHash; } + std::chrono::steady_clock::time_point getLastUpdate() const { return mLastUpdate; } + + private: + const LayerState* mState; + NonBufferHash mHash; + std::chrono::steady_clock::time_point mLastUpdate; + }; + + CachedSet(const LayerState*, std::chrono::steady_clock::time_point lastUpdate); + CachedSet(Layer layer); + + void addLayer(const LayerState*, std::chrono::steady_clock::time_point lastUpdate); + + std::chrono::steady_clock::time_point getLastUpdate() const { return mLastUpdate; } + NonBufferHash getFingerprint() const { return mFingerprint; } + size_t getLayerCount() const { return mLayers.size(); } + const Layer& getFirstLayer() const { return mLayers[0]; } + const Rect& getBounds() const { return mBounds; } + size_t getAge() const { return mAge; } + const sp& getBuffer() const { return mBuffer; } + const sp& getDrawFence() const { return mDrawFence; } + + NonBufferHash getNonBufferHash() const; + + size_t getComponentDisplayCost() const; + size_t getCreationCost() const; + size_t getDisplayCost() const; + + bool hasBufferUpdate(std::vector::const_iterator layers) const; + bool hasReadyBuffer() const; + + // Decomposes this CachedSet into a vector of its layers as individual CachedSets + std::vector decompose() const; + + void updateAge(std::chrono::steady_clock::time_point now); + + void setLastUpdate(std::chrono::steady_clock::time_point now) { mLastUpdate = now; } + void append(const CachedSet& other) { + mBuffer = nullptr; + mDrawFence = nullptr; + + mLayers.insert(mLayers.end(), other.mLayers.cbegin(), other.mLayers.cend()); + Region boundingRegion; + boundingRegion.orSelf(mBounds); + boundingRegion.orSelf(other.mBounds); + mBounds = boundingRegion.getBounds(); + } + void incrementAge() { ++mAge; } + + void render(renderengine::RenderEngine&); + + void dump(std::string& result) const; + +private: + CachedSet() = default; + + NonBufferHash mFingerprint = 0; + std::chrono::steady_clock::time_point mLastUpdate = std::chrono::steady_clock::now(); + std::vector mLayers; + Rect mBounds = Rect::EMPTY_RECT; + size_t mAge = 0; + sp mBuffer; + sp mDrawFence; + + static const bool sDebugHighlighLayers; +}; + +} // namespace compositionengine::impl::planner +} // namespace android diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h new file mode 100644 index 0000000000..6c8640899d --- /dev/null +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h @@ -0,0 +1,86 @@ +/* + * 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 +#include + +#include + +namespace android { + +namespace renderengine { +class RenderEngine; +} // namespace renderengine + +namespace compositionengine::impl::planner { +using namespace std::chrono_literals; + +class LayerState; +class Predictor; + +class Flattener { +public: + Flattener(Predictor& predictor) : mPredictor(predictor) {} + + void setDisplaySize(ui::Size size) { mDisplaySize = size; } + + NonBufferHash flattenLayers(const std::vector& layers, NonBufferHash); + + void renderCachedSets(renderengine::RenderEngine&); + + void reset(); + + void dump(std::string& result) const; + +private: + size_t calculateDisplayCost(const std::vector& layers) const; + + void resetActivities(NonBufferHash, std::chrono::steady_clock::time_point now); + + void updateLayersHash(); + + bool mergeWithCachedSets(const std::vector& layers, + std::chrono::steady_clock::time_point now); + + void buildCachedSets(std::chrono::steady_clock::time_point now); + + Predictor& mPredictor; + + ui::Size mDisplaySize; + + NonBufferHash mCurrentGeometry; + std::chrono::steady_clock::time_point mLastGeometryUpdate; + + std::vector mLayers; + NonBufferHash mLayersHash = 0; + std::optional mNewCachedSet; + + // Statistics + size_t mUnflattenedDisplayCost = 0; + size_t mFlattenedDisplayCost = 0; + std::unordered_map mInitialLayerCounts; + std::unordered_map mFinalLayerCounts; + size_t mCachedSetCreationCount = 0; + size_t mCachedSetCreationCost = 0; + std::unordered_map mInvalidatedCachedSetAges; + + static constexpr auto kActiveLayerTimeout = std::chrono::nanoseconds(150ms); +}; + +} // namespace compositionengine::impl::planner +} // namespace android diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Planner.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Planner.h index ad53d27579..e96abb72f7 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Planner.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Planner.h @@ -17,6 +17,7 @@ #pragma once #include +#include #include #include #include @@ -28,6 +29,10 @@ namespace android { +namespace renderengine { +class RenderEngine; +} // namespace renderengine + namespace compositionengine::impl::planner { // This is the top level class for layer caching. It is responsible for @@ -36,8 +41,15 @@ namespace compositionengine::impl::planner { // as a more efficient representation of parts of the layer stack. class Planner { public: + Planner() : mFlattener(mPredictor) {} + + void setDisplaySize(ui::Size); + // Updates the Planner with the current set of layers before a composition strategy is // determined. + // The Planner will call to the Flattener to determine to: + // 1. Replace any cached sets with a newly available flattened cached set + // 2. Create a new cached set if possible void plan( compositionengine::Output::OutputLayersEnumerator&& layers); @@ -46,6 +58,9 @@ public: void reportFinalPlan( compositionengine::Output::OutputLayersEnumerator&& layers); + // The planner will call to the Flattener to render any pending cached set + void renderCachedSets(renderengine::RenderEngine&); + void dump(const Vector& args, std::string&); private: @@ -56,8 +71,10 @@ private: std::vector mCurrentLayers; Predictor mPredictor; + Flattener mFlattener; std::optional mPredictedPlan; + NonBufferHash mFlattenedHash = 0; }; } // namespace compositionengine::impl::planner diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Predictor.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Predictor.h index 38c73f79c4..422af771f3 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Predictor.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Predictor.h @@ -210,8 +210,8 @@ public: std::optional getPredictedPlan(const std::vector&, NonBufferHash) const; - void recordResult(std::optional predictedPlan, - const std::vector&, Plan result); + void recordResult(std::optional predictedPlan, NonBufferHash flattenedHash, + const std::vector&, bool hasSkippedLayers, Plan result); void dump(std::string&) const; diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h index 92991996d9..5aa53e54dd 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h @@ -107,6 +107,7 @@ public: MOCK_CONST_METHOD0(getSkipColorTransform, bool()); MOCK_METHOD0(postFramebuffer, void()); + MOCK_METHOD0(renderCachedSets, void()); MOCK_METHOD0(presentAndGetFrameFences, compositionengine::Output::FrameFences()); MOCK_METHOD3(generateClientCompositionRequests, diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/OutputLayer.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/OutputLayer.h index 81e1fc7ea0..2454ff7188 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/OutputLayer.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/OutputLayer.h @@ -39,7 +39,7 @@ public: MOCK_METHOD0(editState, impl::OutputLayerCompositionState&()); MOCK_METHOD3(updateCompositionState, void(bool, bool, ui::Transform::RotationFlags)); - MOCK_METHOD1(writeStateToHWC, void(bool)); + MOCK_METHOD2(writeStateToHWC, void(bool, bool)); MOCK_CONST_METHOD0(writeCursorPositionToHWC, void()); MOCK_CONST_METHOD0(getHwcLayer, HWC2::Layer*()); @@ -49,6 +49,7 @@ public: MOCK_METHOD0(prepareForDeviceLayerRequests, void()); MOCK_METHOD1(applyDeviceLayerRequest, void(Hwc2::IComposerClient::LayerRequest request)); MOCK_CONST_METHOD0(needsFiltering, bool()); + MOCK_CONST_METHOD0(getOverrideCompositionList, std::vector()); MOCK_CONST_METHOD1(dump, void(std::string&)); }; diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp index 57b7481b81..dc1aacc758 100644 --- a/services/surfaceflinger/CompositionEngine/src/Output.cpp +++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp @@ -198,6 +198,10 @@ void Output::setDisplaySize(const ui::Size& size) { const Rect newOrientedBounds(orientedSize); state.orientedDisplaySpace.bounds = newOrientedBounds; + if (mPlanner) { + mPlanner->setDisplaySize(size); + } + dirtyEntireOutput(); } @@ -317,7 +321,11 @@ compositionengine::RenderSurface* Output::getRenderSurface() const { void Output::setRenderSurface(std::unique_ptr surface) { mRenderSurface = std::move(surface); - editState().framebufferSpace.bounds = Rect(mRenderSurface->getSize()); + const auto size = mRenderSurface->getSize(); + editState().framebufferSpace.bounds = Rect(size); + if (mPlanner) { + mPlanner->setDisplaySize(size); + } dirtyEntireOutput(); } @@ -402,6 +410,7 @@ void Output::present(const compositionengine::CompositionRefreshArgs& refreshArg devOptRepaintFlash(refreshArgs); finishFrame(refreshArgs); postFramebuffer(); + renderCachedSets(); } void Output::rebuildLayerStacks(const compositionengine::CompositionRefreshArgs& refreshArgs, @@ -701,8 +710,23 @@ void Output::writeCompositionState(const compositionengine::CompositionRefreshAr return; } + sp previousOverride = nullptr; for (auto* layer : getOutputLayersOrderedByZ()) { - layer->writeStateToHWC(refreshArgs.updatingGeometryThisFrame); + bool skipLayer = false; + if (layer->getState().overrideInfo.buffer != nullptr) { + if (previousOverride != nullptr && + layer->getState().overrideInfo.buffer == previousOverride) { + ALOGV("Skipping redundant buffer"); + skipLayer = true; + } + previousOverride = layer->getState().overrideInfo.buffer; + } + + // TODO(b/181172795): We now update geometry for all flattened layers. We should update it + // only when the geometry actually changes + const bool includeGeometry = refreshArgs.updatingGeometryThisFrame || + layer->getState().overrideInfo.buffer != nullptr || skipLayer; + layer->writeStateToHWC(includeGeometry, skipLayer); } } @@ -1126,10 +1150,16 @@ std::vector Output::generateClientCompositionRequests( .realContentIsVisible = realContentIsVisible, .clearContent = !clientComposition, .disableBlurs = disableBlurs}; - std::vector results = - layerFE.prepareClientCompositionList(targetSettings); - if (realContentIsVisible && !results.empty()) { - layer->editState().clientCompositionTimestamp = systemTime(); + + std::vector results; + if (layer->getState().overrideInfo.buffer != nullptr) { + results = layer->getOverrideCompositionList(); + ALOGV("Replacing [%s] with override in RE", layer->getLayerFE().getDebugName()); + } else { + results = layerFE.prepareClientCompositionList(targetSettings); + if (realContentIsVisible && !results.empty()) { + layer->editState().clientCompositionTimestamp = systemTime(); + } } clientCompositionLayers.insert(clientCompositionLayers.end(), @@ -1220,6 +1250,12 @@ void Output::postFramebuffer() { mReleasedLayers.clear(); } +void Output::renderCachedSets() { + if (mPlanner) { + mPlanner->renderCachedSets(getCompositionEngine().getRenderEngine()); + } +} + void Output::dirtyEntireOutput() { auto& outputState = editState(); outputState.dirtyRegion.set(outputState.displaySpace.bounds); diff --git a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp index 0faab6fbf4..54784a2cf5 100644 --- a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp +++ b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp @@ -16,7 +16,6 @@ #include #include -#include #include #include #include @@ -313,7 +312,7 @@ void OutputLayer::updateCompositionState( } } -void OutputLayer::writeStateToHWC(bool includeGeometry) { +void OutputLayer::writeStateToHWC(bool includeGeometry, bool skipLayer) { const auto& state = getState(); // Skip doing this if there is no HWC interface if (!state.hwc) { @@ -336,7 +335,8 @@ void OutputLayer::writeStateToHWC(bool includeGeometry) { if (includeGeometry) { writeOutputDependentGeometryStateToHWC(hwcLayer.get(), requestedCompositionType); - writeOutputIndependentGeometryStateToHWC(hwcLayer.get(), *outputIndependentState); + writeOutputIndependentGeometryStateToHWC(hwcLayer.get(), *outputIndependentState, + skipLayer); } writeOutputDependentPerFrameStateToHWC(hwcLayer.get()); @@ -352,23 +352,27 @@ void OutputLayer::writeOutputDependentGeometryStateToHWC( HWC2::Layer* hwcLayer, hal::Composition requestedCompositionType) { const auto& outputDependentState = getState(); - if (auto error = hwcLayer->setDisplayFrame(outputDependentState.displayFrame); - error != hal::Error::NONE) { + Rect displayFrame = outputDependentState.displayFrame; + FloatRect sourceCrop = outputDependentState.sourceCrop; + if (outputDependentState.overrideInfo.buffer != nullptr) { // adyabr + displayFrame = outputDependentState.overrideInfo.displayFrame; + sourceCrop = displayFrame.toFloatRect(); + } + + ALOGV("Writing display frame [%d, %d, %d, %d]", displayFrame.left, displayFrame.top, + displayFrame.right, displayFrame.bottom); + + if (auto error = hwcLayer->setDisplayFrame(displayFrame); error != hal::Error::NONE) { ALOGE("[%s] Failed to set display frame [%d, %d, %d, %d]: %s (%d)", - getLayerFE().getDebugName(), outputDependentState.displayFrame.left, - outputDependentState.displayFrame.top, outputDependentState.displayFrame.right, - outputDependentState.displayFrame.bottom, to_string(error).c_str(), - static_cast(error)); + getLayerFE().getDebugName(), displayFrame.left, displayFrame.top, displayFrame.right, + displayFrame.bottom, to_string(error).c_str(), static_cast(error)); } - if (auto error = hwcLayer->setSourceCrop(outputDependentState.sourceCrop); - error != hal::Error::NONE) { + if (auto error = hwcLayer->setSourceCrop(sourceCrop); error != hal::Error::NONE) { ALOGE("[%s] Failed to set source crop [%.3f, %.3f, %.3f, %.3f]: " "%s (%d)", - getLayerFE().getDebugName(), outputDependentState.sourceCrop.left, - outputDependentState.sourceCrop.top, outputDependentState.sourceCrop.right, - outputDependentState.sourceCrop.bottom, to_string(error).c_str(), - static_cast(error)); + getLayerFE().getDebugName(), sourceCrop.left, sourceCrop.top, sourceCrop.right, + sourceCrop.bottom, to_string(error).c_str(), static_cast(error)); } if (auto error = hwcLayer->setZOrder(outputDependentState.z); error != hal::Error::NONE) { @@ -389,7 +393,8 @@ void OutputLayer::writeOutputDependentGeometryStateToHWC( } void OutputLayer::writeOutputIndependentGeometryStateToHWC( - HWC2::Layer* hwcLayer, const LayerFECompositionState& outputIndependentState) { + HWC2::Layer* hwcLayer, const LayerFECompositionState& outputIndependentState, + bool skipLayer) { if (auto error = hwcLayer->setBlendMode(outputIndependentState.blendMode); error != hal::Error::NONE) { ALOGE("[%s] Failed to set blend mode %s: %s (%d)", getLayerFE().getDebugName(), @@ -397,10 +402,12 @@ void OutputLayer::writeOutputIndependentGeometryStateToHWC( static_cast(error)); } - if (auto error = hwcLayer->setPlaneAlpha(outputIndependentState.alpha); - error != hal::Error::NONE) { - ALOGE("[%s] Failed to set plane alpha %.3f: %s (%d)", getLayerFE().getDebugName(), - outputIndependentState.alpha, to_string(error).c_str(), static_cast(error)); + const float alpha = skipLayer ? 0.0f : outputIndependentState.alpha; + ALOGV("Writing alpha %f", alpha); + + if (auto error = hwcLayer->setPlaneAlpha(alpha); error != hal::Error::NONE) { + ALOGE("[%s] Failed to set plane alpha %.3f: %s (%d)", getLayerFE().getDebugName(), alpha, + to_string(error).c_str(), static_cast(error)); } for (const auto& [name, entry] : outputIndependentState.metadata) { @@ -509,19 +516,26 @@ void OutputLayer::writeBufferStateToHWC(HWC2::Layer* hwcLayer, to_string(error).c_str(), static_cast(error)); } + sp buffer = outputIndependentState.buffer; + sp acquireFence = outputIndependentState.acquireFence; + if (getState().overrideInfo.buffer != nullptr) { + buffer = getState().overrideInfo.buffer; + acquireFence = getState().overrideInfo.acquireFence; + } + + ALOGV("Writing buffer %p", buffer.get()); + uint32_t hwcSlot = 0; sp hwcBuffer; // We need access to the output-dependent state for the buffer cache there, // though otherwise the buffer is not output-dependent. - editState().hwc->hwcBufferCache.getHwcBuffer(outputIndependentState.bufferSlot, - outputIndependentState.buffer, &hwcSlot, - &hwcBuffer); + editState().hwc->hwcBufferCache.getHwcBuffer(outputIndependentState.bufferSlot, buffer, + &hwcSlot, &hwcBuffer); - if (auto error = hwcLayer->setBuffer(hwcSlot, hwcBuffer, outputIndependentState.acquireFence); + if (auto error = hwcLayer->setBuffer(hwcSlot, hwcBuffer, acquireFence); error != hal::Error::NONE) { - ALOGE("[%s] Failed to set buffer %p: %s (%d)", getLayerFE().getDebugName(), - outputIndependentState.buffer->handle, to_string(error).c_str(), - static_cast(error)); + ALOGE("[%s] Failed to set buffer %p: %s (%d)", getLayerFE().getDebugName(), buffer->handle, + to_string(error).c_str(), static_cast(error)); } } @@ -652,6 +666,26 @@ bool OutputLayer::needsFiltering() const { sourceCrop.getWidth() != displayFrame.getWidth(); } +std::vector OutputLayer::getOverrideCompositionList() const { + if (getState().overrideInfo.buffer == nullptr) { + return {}; + } + + LayerFE::LayerSettings settings; + settings.geometry = renderengine::Geometry{ + .boundaries = getState().overrideInfo.displayFrame.toFloatRect(), + }; + settings.bufferId = getState().overrideInfo.buffer->getId(); + settings.source = + renderengine::PixelSource{.buffer = renderengine::Buffer{ + .buffer = getState().overrideInfo.buffer, + .fence = getState().overrideInfo.acquireFence, + }}; + settings.alpha = 1.0f; + + return {static_cast(settings)}; +} + void OutputLayer::dump(std::string& out) const { using android::base::StringAppendF; diff --git a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp new file mode 100644 index 0000000000..ab3fe9e75a --- /dev/null +++ b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp @@ -0,0 +1,243 @@ +/* + * 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. + */ + +#undef LOG_TAG +#define LOG_TAG "Planner" +// #define LOG_NDEBUG 0 + +#include +#include +#include +#include +#include + +namespace android::compositionengine::impl::planner { + +const bool CachedSet::sDebugHighlighLayers = + base::GetBoolProperty(std::string("debug.sf.layer_caching_highlight"), false); + +std::string durationString(std::chrono::milliseconds duration) { + using namespace std::chrono_literals; + + std::string result; + + if (duration >= 1h) { + const auto hours = std::chrono::duration_cast(duration); + base::StringAppendF(&result, "%d hr ", static_cast(hours.count())); + duration -= hours; + } + if (duration >= 1min) { + const auto minutes = std::chrono::duration_cast(duration); + base::StringAppendF(&result, "%d min ", static_cast(minutes.count())); + duration -= minutes; + } + base::StringAppendF(&result, "%.3f sec ", duration.count() / 1000.0f); + + return result; +} + +CachedSet::Layer::Layer(const LayerState* state, std::chrono::steady_clock::time_point lastUpdate) + : mState(state), mHash(state->getHash(LayerStateField::Buffer)), mLastUpdate(lastUpdate) {} + +CachedSet::CachedSet(const LayerState* layer, std::chrono::steady_clock::time_point lastUpdate) + : mFingerprint(layer->getHash(LayerStateField::Buffer)), mLastUpdate(lastUpdate) { + addLayer(layer, lastUpdate); +} + +CachedSet::CachedSet(Layer layer) + : mFingerprint(layer.getHash()), + mLastUpdate(layer.getLastUpdate()), + mBounds(layer.getDisplayFrame()) { + mLayers.emplace_back(std::move(layer)); +} + +void CachedSet::addLayer(const LayerState* layer, + std::chrono::steady_clock::time_point lastUpdate) { + mLayers.emplace_back(layer, lastUpdate); + + Region boundingRegion; + boundingRegion.orSelf(mBounds); + boundingRegion.orSelf(layer->getDisplayFrame()); + mBounds = boundingRegion.getBounds(); +} + +NonBufferHash CachedSet::getNonBufferHash() const { + if (mLayers.size() == 1) { + return mFingerprint; + } + + // TODO(b/181192080): Add all fields which contribute to geometry of override layer (e.g., + // dataspace) + size_t hash = 0; + android::hashCombineSingle(hash, mBounds); + return hash; +} + +size_t CachedSet::getComponentDisplayCost() const { + size_t displayCost = 0; + + for (const Layer& layer : mLayers) { + displayCost += static_cast(layer.getDisplayFrame().width() * + layer.getDisplayFrame().height()); + } + + return displayCost; +} + +size_t CachedSet::getCreationCost() const { + if (mLayers.size() == 1) { + return 0; + } + + // Reads + size_t creationCost = getComponentDisplayCost(); + + // Write - assumes that the output buffer only gets written once per pixel + creationCost += static_cast(mBounds.width() * mBounds.height()); + + return creationCost; +} + +size_t CachedSet::getDisplayCost() const { + return static_cast(mBounds.width() * mBounds.height()); +} + +bool CachedSet::hasBufferUpdate(std::vector::const_iterator layers) const { + for (const Layer& layer : mLayers) { + if (layer.getFramesSinceBufferUpdate() == 0) { + return true; + } + ++layers; + } + return false; +} + +bool CachedSet::hasReadyBuffer() const { + return mBuffer != nullptr && mDrawFence->getStatus() == Fence::Status::Signaled; +} + +std::vector CachedSet::decompose() const { + std::vector layers; + + std::transform(mLayers.begin(), mLayers.end(), std::back_inserter(layers), + [](Layer layer) { return CachedSet(std::move(layer)); }); + + return layers; +} + +void CachedSet::updateAge(std::chrono::steady_clock::time_point now) { + LOG_ALWAYS_FATAL_IF(mLayers.size() > 1, "[%s] This should only be called on single-layer sets", + __func__); + + if (mLayers[0].getFramesSinceBufferUpdate() == 0) { + mLastUpdate = now; + mAge = 0; + } +} + +void CachedSet::render(renderengine::RenderEngine& renderEngine) { + renderengine::DisplaySettings displaySettings{ + .physicalDisplay = Rect(0, 0, mBounds.getWidth(), mBounds.getHeight()), + .clip = mBounds, + }; + + Region clearRegion = Region::INVALID_REGION; + Rect viewport = mBounds; + LayerFE::ClientCompositionTargetSettings targetSettings{ + .clip = Region(mBounds), + .needsFiltering = false, + .isSecure = true, + .supportsProtectedContent = false, + .clearRegion = clearRegion, + .viewport = viewport, + // TODO(181192086): Propagate the Output's dataspace instead of using UNKNOWN + .dataspace = ui::Dataspace::UNKNOWN, + .realContentIsVisible = true, + .clearContent = false, + .disableBlurs = false, + }; + + std::vector layerSettings; + for (const auto& layer : mLayers) { + const auto clientCompositionList = + layer.getState()->getOutputLayer()->getLayerFE().prepareClientCompositionList( + targetSettings); + layerSettings.insert(layerSettings.end(), clientCompositionList.cbegin(), + clientCompositionList.cend()); + } + + std::vector layerSettingsPointers; + std::transform(layerSettings.cbegin(), layerSettings.cend(), + std::back_inserter(layerSettingsPointers), + [](const renderengine::LayerSettings& settings) { return &settings; }); + + if (sDebugHighlighLayers) { + renderengine::LayerSettings highlight{ + .geometry = + renderengine::Geometry{ + .boundaries = FloatRect(0.0f, 0.0f, + static_cast(mBounds.getWidth()), + static_cast(mBounds.getHeight())), + }, + .source = + renderengine::PixelSource{ + .solidColor = half3(0.25f, 0.0f, 0.5f), + }, + .alpha = half(0.05f), + }; + + layerSettingsPointers.emplace_back(&highlight); + } + + const uint64_t usageFlags = GraphicBuffer::USAGE_HW_RENDER | GraphicBuffer::USAGE_HW_COMPOSER | + GraphicBuffer::USAGE_HW_TEXTURE; + sp buffer = new GraphicBuffer(static_cast(mBounds.getWidth()), + static_cast(mBounds.getHeight()), + HAL_PIXEL_FORMAT_RGBA_8888, 1, usageFlags); + LOG_ALWAYS_FATAL_IF(buffer->initCheck() != OK); + base::unique_fd drawFence; + status_t result = renderEngine.drawLayers(displaySettings, layerSettingsPointers, buffer, false, + base::unique_fd(), &drawFence); + + if (result == NO_ERROR) { + mBuffer = buffer; + mDrawFence = new Fence(drawFence.release()); + } +} + +void CachedSet::dump(std::string& result) const { + const auto now = std::chrono::steady_clock::now(); + + const auto lastUpdate = + std::chrono::duration_cast(now - mLastUpdate); + base::StringAppendF(&result, " + Fingerprint %016zx, last update %sago, age %zd\n", + mFingerprint, durationString(lastUpdate).c_str(), mAge); + + if (mLayers.size() == 1) { + base::StringAppendF(&result, " Layer [%s]\n", mLayers[0].getName().c_str()); + base::StringAppendF(&result, " Buffer %p", mLayers[0].getBuffer().get()); + } else { + result.append(" Cached set of:"); + for (const Layer& layer : mLayers) { + base::StringAppendF(&result, "\n Layer [%s]", layer.getName().c_str()); + } + } + + base::StringAppendF(&result, "\n Creation cost: %zd", getCreationCost()); + base::StringAppendF(&result, "\n Display cost: %zd\n", getDisplayCost()); +} + +} // namespace android::compositionengine::impl::planner diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp new file mode 100644 index 0000000000..0c09714a89 --- /dev/null +++ b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp @@ -0,0 +1,355 @@ +/* + * 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. + */ + +#undef LOG_TAG +#define LOG_TAG "Planner" +// #define LOG_NDEBUG 0 + +#include +#include +#include + +using time_point = std::chrono::steady_clock::time_point; +using namespace std::chrono_literals; + +namespace android::compositionengine::impl::planner { + +NonBufferHash Flattener::flattenLayers(const std::vector& layers, + NonBufferHash hash) { + const auto now = std::chrono::steady_clock::now(); + + const size_t unflattenedDisplayCost = calculateDisplayCost(layers); + mUnflattenedDisplayCost += unflattenedDisplayCost; + + if (mCurrentGeometry != hash) { + resetActivities(hash, now); + mFlattenedDisplayCost += unflattenedDisplayCost; + return hash; + } + + ++mInitialLayerCounts[layers.size()]; + + if (mergeWithCachedSets(layers, now)) { + hash = mLayersHash; + } + + ++mFinalLayerCounts[mLayers.size()]; + + buildCachedSets(now); + + return hash; +} + +void Flattener::renderCachedSets(renderengine::RenderEngine& renderEngine) { + if (!mNewCachedSet) { + return; + } + + mNewCachedSet->render(renderEngine); +} + +void Flattener::reset() { + resetActivities(0, std::chrono::steady_clock::now()); + + mUnflattenedDisplayCost = 0; + mFlattenedDisplayCost = 0; + mInitialLayerCounts.clear(); + mFinalLayerCounts.clear(); + mCachedSetCreationCount = 0; + mCachedSetCreationCost = 0; + mInvalidatedCachedSetAges.clear(); +} + +void Flattener::dump(std::string& result) const { + const auto now = std::chrono::steady_clock::now(); + + base::StringAppendF(&result, "Flattener state:\n"); + + result.append("\n Statistics:\n"); + + result.append(" Display cost (in screen-size buffers):\n"); + const size_t displayArea = static_cast(mDisplaySize.width * mDisplaySize.height); + base::StringAppendF(&result, " Unflattened: %.2f\n", + static_cast(mUnflattenedDisplayCost) / displayArea); + base::StringAppendF(&result, " Flattened: %.2f\n", + static_cast(mFlattenedDisplayCost) / displayArea); + + const auto compareLayerCounts = [](const std::pair& left, + const std::pair& right) { + return left.first < right.first; + }; + + const size_t maxLayerCount = std::max_element(mInitialLayerCounts.cbegin(), + mInitialLayerCounts.cend(), compareLayerCounts) + ->first; + + result.append("\n Initial counts:\n"); + for (size_t count = 1; count < maxLayerCount; ++count) { + size_t initial = mInitialLayerCounts.count(count) > 0 ? mInitialLayerCounts.at(count) : 0; + base::StringAppendF(&result, " % 2zd: %zd\n", count, initial); + } + + result.append("\n Final counts:\n"); + for (size_t count = 1; count < maxLayerCount; ++count) { + size_t final = mFinalLayerCounts.count(count) > 0 ? mFinalLayerCounts.at(count) : 0; + base::StringAppendF(&result, " % 2zd: %zd\n", count, final); + } + + base::StringAppendF(&result, "\n Cached sets created: %zd\n", mCachedSetCreationCount); + base::StringAppendF(&result, " Cost: %.2f\n", + static_cast(mCachedSetCreationCost) / displayArea); + + const auto lastUpdate = + std::chrono::duration_cast(now - mLastGeometryUpdate); + base::StringAppendF(&result, "\n Current hash %016zx, last update %sago\n\n", mCurrentGeometry, + durationString(lastUpdate).c_str()); + + result.append(" Current layers:"); + for (const CachedSet& layer : mLayers) { + result.append("\n"); + layer.dump(result); + } +} + +size_t Flattener::calculateDisplayCost(const std::vector& layers) const { + Region coveredRegion; + size_t displayCost = 0; + bool hasClientComposition = false; + + for (const LayerState* layer : layers) { + coveredRegion.orSelf(layer->getDisplayFrame()); + + // Regardless of composition type, we always have to read each input once + displayCost += static_cast(layer->getDisplayFrame().width() * + layer->getDisplayFrame().height()); + + hasClientComposition |= layer->getCompositionType() == hal::Composition::CLIENT; + } + + if (hasClientComposition) { + // If there is client composition, the client target buffer has to be both written by the + // GPU and read by the DPU, so we pay its cost twice + displayCost += 2 * + static_cast(coveredRegion.bounds().width() * + coveredRegion.bounds().height()); + } + + return displayCost; +} + +void Flattener::resetActivities(NonBufferHash hash, time_point now) { + ALOGV("[%s]", __func__); + + mCurrentGeometry = hash; + mLastGeometryUpdate = now; + + for (const CachedSet& cachedSet : mLayers) { + if (cachedSet.getLayerCount() > 1) { + ++mInvalidatedCachedSetAges[cachedSet.getAge()]; + } + } + + mLayers.clear(); + + if (mNewCachedSet) { + ++mInvalidatedCachedSetAges[mNewCachedSet->getAge()]; + mNewCachedSet = std::nullopt; + } +} + +void Flattener::updateLayersHash() { + size_t hash = 0; + for (const auto& layer : mLayers) { + android::hashCombineSingleHashed(hash, layer.getNonBufferHash()); + } + mLayersHash = hash; +} + +bool Flattener::mergeWithCachedSets(const std::vector& layers, time_point now) { + std::vector merged; + + if (mLayers.empty()) { + merged.reserve(layers.size()); + for (const LayerState* layer : layers) { + merged.emplace_back(layer, now); + mFlattenedDisplayCost += merged.back().getDisplayCost(); + } + mLayers = std::move(merged); + return false; + } + + ALOGV("[%s] Incoming layers:", __func__); + for (const LayerState* layer : layers) { + ALOGV("%s", layer->getName().c_str()); + } + + ALOGV("[%s] Current layers:", __func__); + for (const CachedSet& layer : mLayers) { + std::string dump; + layer.dump(dump); + ALOGV("%s", dump.c_str()); + } + + auto currentLayerIter = mLayers.begin(); + auto incomingLayerIter = layers.begin(); + while (incomingLayerIter != layers.end()) { + if (mNewCachedSet && + mNewCachedSet->getFingerprint() == + (*incomingLayerIter)->getHash(LayerStateField::Buffer)) { + if (mNewCachedSet->hasBufferUpdate(incomingLayerIter)) { + ALOGV("[%s] Dropping new cached set", __func__); + ++mInvalidatedCachedSetAges[0]; + mNewCachedSet = std::nullopt; + } else if (mNewCachedSet->hasReadyBuffer()) { + ALOGV("[%s] Found ready buffer", __func__); + size_t skipCount = mNewCachedSet->getLayerCount(); + while (skipCount != 0) { + const size_t layerCount = currentLayerIter->getLayerCount(); + for (size_t i = 0; i < layerCount; ++i) { + OutputLayer::CompositionState& state = + (*incomingLayerIter)->getOutputLayer()->editState(); + state.overrideInfo = { + .buffer = mNewCachedSet->getBuffer(), + .acquireFence = mNewCachedSet->getDrawFence(), + .displayFrame = mNewCachedSet->getBounds(), + }; + ++incomingLayerIter; + } + + if (currentLayerIter->getLayerCount() > 1) { + ++mInvalidatedCachedSetAges[currentLayerIter->getAge()]; + } + ++currentLayerIter; + + skipCount -= layerCount; + } + merged.emplace_back(std::move(*mNewCachedSet)); + mNewCachedSet = std::nullopt; + continue; + } + } + + if (!currentLayerIter->hasBufferUpdate(incomingLayerIter)) { + currentLayerIter->incrementAge(); + merged.emplace_back(*currentLayerIter); + + // Skip the incoming layers corresponding to this valid current layer + const size_t layerCount = currentLayerIter->getLayerCount(); + for (size_t i = 0; i < layerCount; ++i) { + OutputLayer::CompositionState& state = + (*incomingLayerIter)->getOutputLayer()->editState(); + state.overrideInfo = { + .buffer = currentLayerIter->getBuffer(), + .acquireFence = currentLayerIter->getDrawFence(), + .displayFrame = currentLayerIter->getBounds(), + }; + ++incomingLayerIter; + } + } else if (currentLayerIter->getLayerCount() > 1) { + // Break the current layer into its constituent layers + ++mInvalidatedCachedSetAges[currentLayerIter->getAge()]; + for (CachedSet& layer : currentLayerIter->decompose()) { + layer.updateAge(now); + merged.emplace_back(layer); + ++incomingLayerIter; + } + } else { + currentLayerIter->updateAge(now); + merged.emplace_back(*currentLayerIter); + ++incomingLayerIter; + } + ++currentLayerIter; + } + + for (const CachedSet& layer : merged) { + mFlattenedDisplayCost += layer.getDisplayCost(); + } + + mLayers = std::move(merged); + updateLayersHash(); + return true; +} + +void Flattener::buildCachedSets(time_point now) { + struct Run { + Run(std::vector::const_iterator start, size_t length) + : start(start), length(length) {} + + std::vector::const_iterator start; + size_t length; + }; + + if (mLayers.empty()) { + ALOGV("[%s] No layers found, returning", __func__); + return; + } + + std::vector runs; + bool isPartOfRun = false; + for (auto currentSet = mLayers.cbegin(); currentSet != mLayers.cend(); ++currentSet) { + if (now - currentSet->getLastUpdate() > kActiveLayerTimeout) { + // Layer is inactive + if (isPartOfRun) { + runs.back().length += currentSet->getLayerCount(); + } else { + // Runs can't start with a non-buffer layer + if (currentSet->getFirstLayer().getBuffer() == nullptr) { + ALOGV("[%s] Skipping initial non-buffer layer", __func__); + } else { + runs.emplace_back(currentSet, currentSet->getLayerCount()); + isPartOfRun = true; + } + } + } else { + // Runs must be at least 2 sets long or there's nothing to combine + if (isPartOfRun && runs.back().start->getLayerCount() == runs.back().length) { + runs.pop_back(); + } + + isPartOfRun = false; + } + } + + // Check for at least 2 sets one more time in case the set includes the last layer + if (isPartOfRun && runs.back().start->getLayerCount() == runs.back().length) { + runs.pop_back(); + } + + ALOGV("[%s] Found %zu candidate runs", __func__, runs.size()); + + if (runs.empty()) { + return; + } + + mNewCachedSet.emplace(*runs[0].start); + mNewCachedSet->setLastUpdate(now); + auto currentSet = runs[0].start; + while (mNewCachedSet->getLayerCount() < runs[0].length) { + ++currentSet; + mNewCachedSet->append(*currentSet); + } + + // TODO(b/181192467): Actually compute new LayerState vector and corresponding hash for each run + mPredictor.getPredictedPlan({}, 0); + + ++mCachedSetCreationCount; + mCachedSetCreationCost += mNewCachedSet->getCreationCost(); + std::string setDump; + mNewCachedSet->dump(setDump); + ALOGV("[%s] Added new cached set:\n%s", __func__, setDump.c_str()); +} + +} // namespace android::compositionengine::impl::planner diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp index 619d008e41..52efff5e39 100644 --- a/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp +++ b/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp @@ -19,10 +19,16 @@ #undef LOG_TAG #define LOG_TAG "Planner" +#include +#include #include namespace android::compositionengine::impl::planner { +void Planner::setDisplaySize(ui::Size size) { + mFlattener.setDisplaySize(size); +} + void Planner::plan( compositionengine::Output::OutputLayersEnumerator&& layers) { std::unordered_set removedLayers; @@ -39,7 +45,9 @@ void Planner::plan( // Track changes from previous info LayerState& state = layerEntry->second; Flags differences = state.update(layer); - if (differences.get() != 0) { + if (differences.get() == 0) { + state.incrementFramesSinceBufferUpdate(); + } else { ALOGV("Layer %s changed: %s", state.getName().c_str(), differences.string().c_str()); @@ -74,10 +82,21 @@ void Planner::plan( mCurrentLayers.clear(); mCurrentLayers.reserve(currentLayerIds.size()); std::transform(currentLayerIds.cbegin(), currentLayerIds.cend(), - std::back_inserter(mCurrentLayers), - [this](LayerId id) { return &mPreviousLayers.at(id); }); - - mPredictedPlan = mPredictor.getPredictedPlan(mCurrentLayers, getNonBufferHash(mCurrentLayers)); + std::back_inserter(mCurrentLayers), [this](LayerId id) { + LayerState* state = &mPreviousLayers.at(id); + state->getOutputLayer()->editState().overrideInfo = {}; + return state; + }); + + const NonBufferHash hash = getNonBufferHash(mCurrentLayers); + mFlattenedHash = mFlattener.flattenLayers(mCurrentLayers, hash); + const bool layersWereFlattened = hash != mFlattenedHash; + ALOGV("[%s] Initial hash %zx flattened hash %zx", __func__, hash, mFlattenedHash); + + mPredictedPlan = + mPredictor.getPredictedPlan(layersWereFlattened ? std::vector() + : mCurrentLayers, + mFlattenedHash); if (mPredictedPlan) { ALOGV("[%s] Predicting plan %s", __func__, to_string(mPredictedPlan->plan).c_str()); } else { @@ -88,7 +107,18 @@ void Planner::plan( void Planner::reportFinalPlan( compositionengine::Output::OutputLayersEnumerator&& layers) { Plan finalPlan; + const GraphicBuffer* currentOverrideBuffer = nullptr; + bool hasSkippedLayers = false; for (auto layer : layers) { + const GraphicBuffer* overrideBuffer = layer->getState().overrideInfo.buffer.get(); + if (overrideBuffer != nullptr && overrideBuffer == currentOverrideBuffer) { + // Skip this layer since it is part of a previous cached set + hasSkippedLayers = true; + continue; + } + + currentOverrideBuffer = overrideBuffer; + const bool forcedOrRequestedClient = layer->getState().forceClientComposition || layer->requiresClientComposition(); @@ -98,7 +128,12 @@ void Planner::reportFinalPlan( : layer->getLayerFE().getCompositionState()->compositionType); } - mPredictor.recordResult(mPredictedPlan, mCurrentLayers, finalPlan); + mPredictor.recordResult(mPredictedPlan, mFlattenedHash, mCurrentLayers, hasSkippedLayers, + finalPlan); +} + +void Planner::renderCachedSets(renderengine::RenderEngine& renderEngine) { + mFlattener.renderCachedSets(renderEngine); } void Planner::dump(const Vector& args, std::string& result) { @@ -194,6 +229,10 @@ void Planner::dump(const Vector& args, std::string& result) { } // If there are no specific commands, dump the usual state + + mFlattener.dump(result); + result.append("\n"); + mPredictor.dump(result); } diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Predictor.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Predictor.cpp index 28be9db8cf..ba5e64dec6 100644 --- a/services/surfaceflinger/CompositionEngine/src/planner/Predictor.cpp +++ b/services/surfaceflinger/CompositionEngine/src/planner/Predictor.cpp @@ -176,7 +176,9 @@ std::optional Predictor::getPredictedPlan( } void Predictor::recordResult(std::optional predictedPlan, - const std::vector& layers, Plan result) { + NonBufferHash flattenedHash, + const std::vector& layers, bool hasSkippedLayers, + Plan result) { if (predictedPlan) { recordPredictedResult(*predictedPlan, layers, std::move(result)); return; @@ -184,14 +186,12 @@ void Predictor::recordResult(std::optional predictedPlan, ++mMissCount; - if (findSimilarPrediction(layers, result)) { + if (!hasSkippedLayers && findSimilarPrediction(layers, result)) { return; } - const NonBufferHash hash = getNonBufferHash(layers); - - ALOGV("[%s] Adding novel candidate %zx", __func__, hash); - mCandidates.emplace_front(hash, Prediction(layers, result)); + ALOGV("[%s] Adding novel candidate %zx", __func__, flattenedHash); + mCandidates.emplace_front(flattenedHash, Prediction(layers, result)); if (mCandidates.size() > MAX_CANDIDATES) { mCandidates.pop_back(); } @@ -476,4 +476,4 @@ void Predictor::dumpPredictionsByFrequency(std::string& result) const { } } -} // namespace android::compositionengine::impl::planner \ No newline at end of file +} // namespace android::compositionengine::impl::planner diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp index dcfc162922..9dd199db6c 100644 --- a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp @@ -840,19 +840,19 @@ const std::vector OutputLayerWriteStateToHWCTest::kLayerGenericMetadata TEST_F(OutputLayerWriteStateToHWCTest, doesNothingIfNoFECompositionState) { EXPECT_CALL(*mLayerFE, getCompositionState()).WillOnce(Return(nullptr)); - mOutputLayer.writeStateToHWC(true); + mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false); } TEST_F(OutputLayerWriteStateToHWCTest, doesNothingIfNoHWCState) { mOutputLayer.editState().hwc.reset(); - mOutputLayer.writeStateToHWC(true); + mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false); } TEST_F(OutputLayerWriteStateToHWCTest, doesNothingIfNoHWCLayer) { mOutputLayer.editState().hwc = impl::OutputLayerCompositionState::Hwc(nullptr); - mOutputLayer.writeStateToHWC(true); + mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false); } TEST_F(OutputLayerWriteStateToHWCTest, canSetAllState) { @@ -861,7 +861,7 @@ TEST_F(OutputLayerWriteStateToHWCTest, canSetAllState) { expectNoSetCompositionTypeCall(); - mOutputLayer.writeStateToHWC(true); + mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false); } TEST_F(OutputLayerTest, displayInstallOrientationBufferTransformSetTo90) { @@ -891,7 +891,7 @@ TEST_F(OutputLayerWriteStateToHWCTest, canSetPerFrameStateForSolidColor) { expectSetCompositionTypeCall(Hwc2::IComposerClient::Composition::SOLID_COLOR); expectSetColorCall(); - mOutputLayer.writeStateToHWC(false); + mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false); } TEST_F(OutputLayerWriteStateToHWCTest, canSetPerFrameStateForSideband) { @@ -901,7 +901,7 @@ TEST_F(OutputLayerWriteStateToHWCTest, canSetPerFrameStateForSideband) { expectSetSidebandHandleCall(); expectSetCompositionTypeCall(Hwc2::IComposerClient::Composition::SIDEBAND); - mOutputLayer.writeStateToHWC(false); + mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false); } TEST_F(OutputLayerWriteStateToHWCTest, canSetPerFrameStateForCursor) { @@ -911,7 +911,7 @@ TEST_F(OutputLayerWriteStateToHWCTest, canSetPerFrameStateForCursor) { expectSetHdrMetadataAndBufferCalls(); expectSetCompositionTypeCall(Hwc2::IComposerClient::Composition::CURSOR); - mOutputLayer.writeStateToHWC(false); + mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false); } TEST_F(OutputLayerWriteStateToHWCTest, canSetPerFrameStateForDevice) { @@ -921,7 +921,7 @@ TEST_F(OutputLayerWriteStateToHWCTest, canSetPerFrameStateForDevice) { expectSetHdrMetadataAndBufferCalls(); expectSetCompositionTypeCall(Hwc2::IComposerClient::Composition::DEVICE); - mOutputLayer.writeStateToHWC(false); + mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false); } TEST_F(OutputLayerWriteStateToHWCTest, compositionTypeIsNotSetIfUnchanged) { @@ -934,7 +934,7 @@ TEST_F(OutputLayerWriteStateToHWCTest, compositionTypeIsNotSetIfUnchanged) { expectSetColorCall(); expectNoSetCompositionTypeCall(); - mOutputLayer.writeStateToHWC(false); + mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false); } TEST_F(OutputLayerWriteStateToHWCTest, compositionTypeIsSetToClientIfColorTransformNotSupported) { @@ -944,7 +944,7 @@ TEST_F(OutputLayerWriteStateToHWCTest, compositionTypeIsSetToClientIfColorTransf expectSetColorCall(); expectSetCompositionTypeCall(Hwc2::IComposerClient::Composition::CLIENT); - mOutputLayer.writeStateToHWC(false); + mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false); } TEST_F(OutputLayerWriteStateToHWCTest, compositionTypeIsSetToClientIfClientCompositionForced) { @@ -956,7 +956,7 @@ TEST_F(OutputLayerWriteStateToHWCTest, compositionTypeIsSetToClientIfClientCompo expectSetColorCall(); expectSetCompositionTypeCall(Hwc2::IComposerClient::Composition::CLIENT); - mOutputLayer.writeStateToHWC(false); + mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false); } TEST_F(OutputLayerWriteStateToHWCTest, allStateIncludesMetadataIfPresent) { @@ -969,7 +969,7 @@ TEST_F(OutputLayerWriteStateToHWCTest, allStateIncludesMetadataIfPresent) { expectGenericLayerMetadataCalls(); expectSetCompositionTypeCall(Hwc2::IComposerClient::Composition::DEVICE); - mOutputLayer.writeStateToHWC(true); + mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false); } TEST_F(OutputLayerWriteStateToHWCTest, perFrameStateDoesNotIncludeMetadataIfPresent) { @@ -980,7 +980,7 @@ TEST_F(OutputLayerWriteStateToHWCTest, perFrameStateDoesNotIncludeMetadataIfPres expectSetHdrMetadataAndBufferCalls(); expectSetCompositionTypeCall(Hwc2::IComposerClient::Composition::DEVICE); - mOutputLayer.writeStateToHWC(false); + mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false); } /* diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp index 8dcace6b6c..3059beb9e8 100644 --- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp @@ -783,11 +783,14 @@ TEST_F(OutputUpdateAndWriteCompositionStateTest, updatesLayerContentForAllLayers InjectedLayer layer3; EXPECT_CALL(*layer1.outputLayer, updateCompositionState(false, false, ui::Transform::ROT_180)); - EXPECT_CALL(*layer1.outputLayer, writeStateToHWC(false)); + EXPECT_CALL(*layer1.outputLayer, + writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false)); EXPECT_CALL(*layer2.outputLayer, updateCompositionState(false, false, ui::Transform::ROT_180)); - EXPECT_CALL(*layer2.outputLayer, writeStateToHWC(false)); + EXPECT_CALL(*layer2.outputLayer, + writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false)); EXPECT_CALL(*layer3.outputLayer, updateCompositionState(false, false, ui::Transform::ROT_180)); - EXPECT_CALL(*layer3.outputLayer, writeStateToHWC(false)); + EXPECT_CALL(*layer3.outputLayer, + writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false)); injectOutputLayer(layer1); injectOutputLayer(layer2); @@ -810,11 +813,14 @@ TEST_F(OutputUpdateAndWriteCompositionStateTest, updatesLayerGeometryAndContentF InjectedLayer layer3; EXPECT_CALL(*layer1.outputLayer, updateCompositionState(true, false, ui::Transform::ROT_0)); - EXPECT_CALL(*layer1.outputLayer, writeStateToHWC(true)); + EXPECT_CALL(*layer1.outputLayer, + writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false)); EXPECT_CALL(*layer2.outputLayer, updateCompositionState(true, false, ui::Transform::ROT_0)); - EXPECT_CALL(*layer2.outputLayer, writeStateToHWC(true)); + EXPECT_CALL(*layer2.outputLayer, + writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false)); EXPECT_CALL(*layer3.outputLayer, updateCompositionState(true, false, ui::Transform::ROT_0)); - EXPECT_CALL(*layer3.outputLayer, writeStateToHWC(true)); + EXPECT_CALL(*layer3.outputLayer, + writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false)); injectOutputLayer(layer1); injectOutputLayer(layer2); @@ -836,11 +842,14 @@ TEST_F(OutputUpdateAndWriteCompositionStateTest, forcesClientCompositionForAllLa InjectedLayer layer3; EXPECT_CALL(*layer1.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0)); - EXPECT_CALL(*layer1.outputLayer, writeStateToHWC(false)); + EXPECT_CALL(*layer1.outputLayer, + writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false)); EXPECT_CALL(*layer2.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0)); - EXPECT_CALL(*layer2.outputLayer, writeStateToHWC(false)); + EXPECT_CALL(*layer2.outputLayer, + writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false)); EXPECT_CALL(*layer3.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0)); - EXPECT_CALL(*layer3.outputLayer, writeStateToHWC(false)); + EXPECT_CALL(*layer3.outputLayer, + writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false)); injectOutputLayer(layer1); injectOutputLayer(layer2); @@ -1648,6 +1657,7 @@ struct OutputPresentTest : public testing::Test { MOCK_METHOD1(devOptRepaintFlash, void(const compositionengine::CompositionRefreshArgs&)); MOCK_METHOD1(finishFrame, void(const compositionengine::CompositionRefreshArgs&)); MOCK_METHOD0(postFramebuffer, void()); + MOCK_METHOD0(renderCachedSets, void()); }; StrictMock mOutput; @@ -1667,6 +1677,7 @@ TEST_F(OutputPresentTest, justInvokesChildFunctionsInSequence) { EXPECT_CALL(mOutput, devOptRepaintFlash(Ref(args))); EXPECT_CALL(mOutput, finishFrame(Ref(args))); EXPECT_CALL(mOutput, postFramebuffer()); + EXPECT_CALL(mOutput, renderCachedSets()); mOutput.present(args); } @@ -3482,7 +3493,8 @@ struct OutputComposeSurfacesTest_SetsExpensiveRendering_ForBlur mOutput.editState().isEnabled = true; EXPECT_CALL(mLayer.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0)); - EXPECT_CALL(mLayer.outputLayer, writeStateToHWC(false)); + EXPECT_CALL(mLayer.outputLayer, + writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false)); EXPECT_CALL(mOutput, generateClientCompositionRequests(_, _, kDefaultOutputDataspace)) .WillOnce(Return(std::vector{})); EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, false, _, _)).WillOnce(Return(NO_ERROR)); @@ -4063,11 +4075,14 @@ TEST_F(OutputUpdateAndWriteCompositionStateTest, handlesBackgroundBlurRequests) // Layer requesting blur, or below, should request client composition. EXPECT_CALL(*layer1.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0)); - EXPECT_CALL(*layer1.outputLayer, writeStateToHWC(false)); + EXPECT_CALL(*layer1.outputLayer, + writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false)); EXPECT_CALL(*layer2.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0)); - EXPECT_CALL(*layer2.outputLayer, writeStateToHWC(false)); + EXPECT_CALL(*layer2.outputLayer, + writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false)); EXPECT_CALL(*layer3.outputLayer, updateCompositionState(false, false, ui::Transform::ROT_0)); - EXPECT_CALL(*layer3.outputLayer, writeStateToHWC(false)); + EXPECT_CALL(*layer3.outputLayer, + writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false)); layer2.layerFEState.backgroundBlurRadius = 10; @@ -4092,11 +4107,14 @@ TEST_F(OutputUpdateAndWriteCompositionStateTest, handlesBlurRegionRequests) { // Layer requesting blur, or below, should request client composition. EXPECT_CALL(*layer1.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0)); - EXPECT_CALL(*layer1.outputLayer, writeStateToHWC(false)); + EXPECT_CALL(*layer1.outputLayer, + writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false)); EXPECT_CALL(*layer2.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0)); - EXPECT_CALL(*layer2.outputLayer, writeStateToHWC(false)); + EXPECT_CALL(*layer2.outputLayer, + writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false)); EXPECT_CALL(*layer3.outputLayer, updateCompositionState(false, false, ui::Transform::ROT_0)); - EXPECT_CALL(*layer3.outputLayer, writeStateToHWC(false)); + EXPECT_CALL(*layer3.outputLayer, + writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false)); BlurRegion region; layer2.layerFEState.blurRegions.push_back(region); -- cgit v1.2.3-59-g8ed1b