diff options
| author | 2021-02-26 01:28:19 +0000 | |
|---|---|---|
| committer | 2021-02-26 01:28:19 +0000 | |
| commit | 2ca7b8f9b1e4d00c055a03c6950cf874aeb0a80c (patch) | |
| tree | 1b5e6cb9adf73f7cba894ff8e3bcb713a9226108 | |
| parent | 834da01d354176d7775131b2a3f7d443a757b46b (diff) | |
| parent | 6166c312bc1e349623a493c683034dd1a2ac4869 (diff) | |
Merge changes from topics "sf_flattener", "sf_planner" into sc-dev
* changes:
SF: Add Planner flattener
SF: Add Planner predictor
20 files changed, 1963 insertions, 71 deletions
diff --git a/services/surfaceflinger/CompositionEngine/Android.bp b/services/surfaceflinger/CompositionEngine/Android.bp index 4d7c8dc133..8556a1c186 100644 --- a/services/surfaceflinger/CompositionEngine/Android.bp +++ b/services/surfaceflinger/CompositionEngine/Android.bp @@ -43,8 +43,11 @@ 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", "src/ClientCompositionRequestCache.cpp", "src/CompositionEngine.cpp", "src/Display.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<base::unique_fd> 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<LayerFE::LayerSettings> 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 ae35fb0489..eeb20fc1d1 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h @@ -94,12 +94,14 @@ public: std::optional<base::unique_fd> composeSurfaces( const Region&, const compositionengine::CompositionRefreshArgs& refreshArgs) override; void postFramebuffer() override; + void renderCachedSets() override; void cacheClientCompositionRequests(uint32_t) override; // Testing const ReleasedLayers& getReleasedLayersForTest() const; void setDisplayColorProfileForTest(std::unique_ptr<compositionengine::DisplayColorProfile>); void setRenderSurfaceForTest(std::unique_ptr<compositionengine::RenderSurface>); + bool plannerEnabled() const { return mPlanner != nullptr; } protected: std::unique_ptr<compositionengine::OutputLayer> createOutputLayer(const sp<LayerFE>&) const; 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 <memory> #include <string> +#include <compositionengine/LayerFE.h> #include <compositionengine/OutputLayer.h> #include <ui/FloatRect.h> #include <ui/Rect.h> @@ -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<LayerFE::LayerSettings> 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<GraphicBuffer> buffer = nullptr; + sp<Fence> 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 <compositionengine/impl/planner/LayerState.h> + +#include <chrono> + +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<GraphicBuffer>& 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<GraphicBuffer>& getBuffer() const { return mBuffer; } + const sp<Fence>& getDrawFence() const { return mDrawFence; } + + NonBufferHash getNonBufferHash() const; + + size_t getComponentDisplayCost() const; + size_t getCreationCost() const; + size_t getDisplayCost() const; + + bool hasBufferUpdate(std::vector<const LayerState*>::const_iterator layers) const; + bool hasReadyBuffer() const; + + // Decomposes this CachedSet into a vector of its layers as individual CachedSets + std::vector<CachedSet> 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<Layer> mLayers; + Rect mBounds = Rect::EMPTY_RECT; + size_t mAge = 0; + sp<GraphicBuffer> mBuffer; + sp<Fence> 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 <compositionengine/impl/planner/CachedSet.h> +#include <compositionengine/impl/planner/LayerState.h> + +#include <vector> + +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<const LayerState*>& layers, NonBufferHash); + + void renderCachedSets(renderengine::RenderEngine&); + + void reset(); + + void dump(std::string& result) const; + +private: + size_t calculateDisplayCost(const std::vector<const LayerState*>& layers) const; + + void resetActivities(NonBufferHash, std::chrono::steady_clock::time_point now); + + void updateLayersHash(); + + bool mergeWithCachedSets(const std::vector<const LayerState*>& 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<CachedSet> mLayers; + NonBufferHash mLayersHash = 0; + std::optional<CachedSet> mNewCachedSet; + + // Statistics + size_t mUnflattenedDisplayCost = 0; + size_t mFlattenedDisplayCost = 0; + std::unordered_map<size_t, size_t> mInitialLayerCounts; + std::unordered_map<size_t, size_t> mFinalLayerCounts; + size_t mCachedSetCreationCount = 0; + size_t mCachedSetCreationCost = 0; + std::unordered_map<size_t, size_t> 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 53eca01aa4..e96abb72f7 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Planner.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Planner.h @@ -17,15 +17,22 @@ #pragma once #include <compositionengine/Output.h> +#include <compositionengine/impl/planner/Flattener.h> #include <compositionengine/impl/planner/LayerState.h> +#include <compositionengine/impl/planner/Predictor.h> #include <utils/String16.h> #include <utils/Vector.h> +#include <optional> #include <string> #include <unordered_map> 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 @@ -34,15 +41,40 @@ 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<compositionengine::Output>&& layers); + // Updates the Planner with the current set of layers after a composition strategy is + // determined. + void reportFinalPlan( + compositionengine::Output::OutputLayersEnumerator<compositionengine::Output>&& layers); + + // The planner will call to the Flattener to render any pending cached set + void renderCachedSets(renderengine::RenderEngine&); + void dump(const Vector<String16>& args, std::string&); private: + void dumpUsage(std::string&) const; + std::unordered_map<LayerId, LayerState> mPreviousLayers; + + std::vector<const LayerState*> mCurrentLayers; + + Predictor mPredictor; + Flattener mFlattener; + + std::optional<Predictor::PredictedPlan> 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 new file mode 100644 index 0000000000..422af771f3 --- /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 <compositionengine/impl/planner/LayerState.h> + +namespace android::compositionengine::impl::planner { + +class LayerStack { +public: + LayerStack(const std::vector<const LayerState*>& layers) : mLayers(copyLayers(layers)) {} + + struct ApproximateMatch { + bool operator==(const ApproximateMatch& other) const { + return differingIndex == other.differingIndex && + differingFields == other.differingFields; + } + + size_t differingIndex; + Flags<LayerStateField> differingFields; + }; + + std::optional<ApproximateMatch> getApproximateMatch( + const std::vector<const LayerState*>& other) const; + + void compare(const LayerStack& other, std::string& result) const { + if (mLayers.size() != other.mLayers.size()) { + base::StringAppendF(&result, "Cannot compare stacks of different sizes (%zd vs. %zd)\n", + mLayers.size(), other.mLayers.size()); + return; + } + + for (size_t l = 0; l < mLayers.size(); ++l) { + const auto& thisLayer = mLayers[l]; + const auto& otherLayer = other.mLayers[l]; + base::StringAppendF(&result, "\n+ - - - - - - - - - Layer %d [%s]\n", thisLayer.getId(), + thisLayer.getName().c_str()); + auto comparisonOpt = thisLayer.compare(otherLayer); + base::StringAppendF(&result, + " %s + - - - - - - - - - - - - - - - - - - - - - - - " + "- Layer %d [%s]\n", + comparisonOpt ? " " : "Identical", otherLayer.getId(), + otherLayer.getName().c_str()); + if (comparisonOpt) { + result.append(*comparisonOpt); + } + } + } + + void dump(std::string& result) const { + for (const LayerState& layer : mLayers) { + base::StringAppendF(&result, "+ - - - - - - - - - Layer %d [%s]\n", layer.getId(), + layer.getName().c_str()); + layer.dump(result); + } + } + + void dumpLayerNames(std::string& result, const std::string& prefix = " ") const { + for (const LayerState& layer : mLayers) { + result.append(prefix); + result.append(layer.getName()); + result.append("\n"); + } + } + +private: + std::vector<const LayerState> copyLayers(const std::vector<const LayerState*>& layers) { + std::vector<const LayerState> copiedLayers; + copiedLayers.reserve(layers.size()); + std::transform(layers.cbegin(), layers.cend(), std::back_inserter(copiedLayers), + [](const LayerState* layerState) { return *layerState; }); + return copiedLayers; + } + + std::vector<const LayerState> mLayers; + + // TODO(b/180976743): Tune kMaxDifferingFields + constexpr static int kMaxDifferingFields = 6; +}; + +class Plan { +public: + static std::optional<Plan> fromString(const std::string&); + + void reset() { mLayerTypes.clear(); } + void addLayerType(hardware::graphics::composer::hal::Composition type) { + mLayerTypes.emplace_back(type); + } + + friend std::string to_string(const Plan& plan); + + friend bool operator==(const Plan& lhs, const Plan& rhs) { + return lhs.mLayerTypes == rhs.mLayerTypes; + } + friend bool operator!=(const Plan& lhs, const Plan& rhs) { return !(lhs == rhs); } + +private: + std::vector<hardware::graphics::composer::hal::Composition> mLayerTypes; +}; + +} // namespace android::compositionengine::impl::planner + +namespace std { +template <> +struct hash<android::compositionengine::impl::planner::Plan> { + size_t operator()(const android::compositionengine::impl::planner::Plan& plan) const { + return std::hash<std::string>{}(to_string(plan)); + } +}; +} // namespace std + +namespace android::compositionengine::impl::planner { + +class Prediction { +public: + enum class Type { + Exact, + Approximate, + Total, + }; + + friend std::string to_string(Type type) { + using namespace std::string_literals; + + switch (type) { + case Type::Exact: + return "Exact"; + case Type::Approximate: + return "Approximate"; + case Type::Total: + return "Total"; + } + } + + Prediction(const std::vector<const LayerState*>& layers, Plan plan) + : mExampleLayerStack(layers), mPlan(std::move(plan)) {} + + const LayerStack& getExampleLayerStack() const { return mExampleLayerStack; } + const Plan& getPlan() const { return mPlan; } + + size_t getHitCount(Type type) const { + if (type == Type::Total) { + return getHitCount(Type::Exact) + getHitCount(Type::Approximate); + } + return getStatsForType(type).hitCount; + } + + size_t getMissCount(Type type) const { + if (type == Type::Total) { + return getMissCount(Type::Exact) + getMissCount(Type::Approximate); + } + return getStatsForType(type).missCount; + } + + void recordHit(Type type) { ++getStatsForType(type).hitCount; } + + void recordMiss(Type type) { ++getStatsForType(type).missCount; } + + void dump(std::string&) const; + +private: + struct Stats { + void dump(std::string& result) const { + const size_t totalAttempts = hitCount + missCount; + base::StringAppendF(&result, "%.2f%% (%zd/%zd)", 100.0f * hitCount / totalAttempts, + hitCount, totalAttempts); + } + + size_t hitCount = 0; + size_t missCount = 0; + }; + + const Stats& getStatsForType(Type type) const { + return (type == Type::Exact) ? mExactStats : mApproximateStats; + } + + Stats& getStatsForType(Type type) { + return const_cast<Stats&>(const_cast<const Prediction*>(this)->getStatsForType(type)); + } + + LayerStack mExampleLayerStack; + Plan mPlan; + + Stats mExactStats; + Stats mApproximateStats; +}; + +class Predictor { +public: + struct PredictedPlan { + NonBufferHash hash; + Plan plan; + Prediction::Type type; + }; + + std::optional<PredictedPlan> getPredictedPlan(const std::vector<const LayerState*>&, + NonBufferHash) const; + + void recordResult(std::optional<PredictedPlan> predictedPlan, NonBufferHash flattenedHash, + const std::vector<const LayerState*>&, bool hasSkippedLayers, Plan result); + + void dump(std::string&) const; + + void compareLayerStacks(NonBufferHash leftHash, NonBufferHash rightHash, std::string&) const; + void describeLayerStack(NonBufferHash, std::string&) const; + void listSimilarStacks(Plan, std::string&) const; + +private: + // Retrieves a prediction from either the main prediction list or from the candidate list + const Prediction& getPrediction(NonBufferHash) const; + Prediction& getPrediction(NonBufferHash); + + std::optional<Plan> getExactMatch(NonBufferHash) const; + std::optional<NonBufferHash> getApproximateMatch( + const std::vector<const LayerState*>& layers) const; + + void promoteIfCandidate(NonBufferHash); + void recordPredictedResult(PredictedPlan, const std::vector<const LayerState*>& layers, + Plan result); + bool findSimilarPrediction(const std::vector<const LayerState*>& layers, Plan result); + + void dumpPredictionsByFrequency(std::string&) const; + + struct PromotionCandidate { + PromotionCandidate(NonBufferHash hash, Prediction&& prediction) + : hash(hash), prediction(std::move(prediction)) {} + + NonBufferHash hash; + Prediction prediction; + }; + + static constexpr const size_t MAX_CANDIDATES = 4; + std::deque<PromotionCandidate> mCandidates; + decltype(mCandidates)::const_iterator getCandidateEntryByHash(NonBufferHash hash) const { + const auto candidateMatches = [&](const PromotionCandidate& candidate) { + return candidate.hash == hash; + }; + + return std::find_if(mCandidates.cbegin(), mCandidates.cend(), candidateMatches); + } + + std::unordered_map<NonBufferHash, Prediction> mPredictions; + std::unordered_map<Plan, std::vector<NonBufferHash>> mSimilarStacks; + + struct ApproximateStack { + ApproximateStack(NonBufferHash hash, LayerStack::ApproximateMatch match) + : hash(hash), match(match) {} + + bool operator==(const ApproximateStack& other) const { + return hash == other.hash && match == other.match; + } + + NonBufferHash hash; + LayerStack::ApproximateMatch match; + }; + + std::vector<ApproximateStack> mApproximateStacks; + + mutable size_t mExactHitCount = 0; + mutable size_t mApproximateHitCount = 0; + mutable size_t mMissCount = 0; +}; + +} // namespace android::compositionengine::impl::planner 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<LayerFE::LayerSettings>()); MOCK_CONST_METHOD1(dump, void(std::string&)); }; diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp index 709d7c9f48..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<compositionengine::RenderSurface> 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<GraphicBuffer> 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); } } @@ -873,6 +897,10 @@ void Output::prepareFrame() { chooseCompositionStrategy(); + if (mPlanner) { + mPlanner->reportFinalPlan(getOutputLayersOrderedByZ()); + } + mRenderSurface->prepareFrame(outputState.usesClientComposition, outputState.usesDeviceComposition); } @@ -1122,10 +1150,16 @@ std::vector<LayerFE::LayerSettings> Output::generateClientCompositionRequests( .realContentIsVisible = realContentIsVisible, .clearContent = !clientComposition, .disableBlurs = disableBlurs}; - std::vector<LayerFE::LayerSettings> results = - layerFE.prepareClientCompositionList(targetSettings); - if (realContentIsVisible && !results.empty()) { - layer->editState().clientCompositionTimestamp = systemTime(); + + std::vector<LayerFE::LayerSettings> 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(), @@ -1216,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 <android-base/stringprintf.h> #include <compositionengine/DisplayColorProfile.h> -#include <compositionengine/LayerFE.h> #include <compositionengine/LayerFECompositionState.h> #include <compositionengine/Output.h> #include <compositionengine/impl/OutputCompositionState.h> @@ -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<int32_t>(error)); + getLayerFE().getDebugName(), displayFrame.left, displayFrame.top, displayFrame.right, + displayFrame.bottom, to_string(error).c_str(), static_cast<int32_t>(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<int32_t>(error)); + getLayerFE().getDebugName(), sourceCrop.left, sourceCrop.top, sourceCrop.right, + sourceCrop.bottom, to_string(error).c_str(), static_cast<int32_t>(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<int32_t>(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<int32_t>(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<int32_t>(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<int32_t>(error)); } + sp<GraphicBuffer> buffer = outputIndependentState.buffer; + sp<Fence> 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<GraphicBuffer> 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<int32_t>(error)); + ALOGE("[%s] Failed to set buffer %p: %s (%d)", getLayerFE().getDebugName(), buffer->handle, + to_string(error).c_str(), static_cast<int32_t>(error)); } } @@ -652,6 +666,26 @@ bool OutputLayer::needsFiltering() const { sourceCrop.getWidth() != displayFrame.getWidth(); } +std::vector<LayerFE::LayerSettings> 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<LayerFE::LayerSettings>(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 <android-base/properties.h> +#include <compositionengine/impl/planner/CachedSet.h> +#include <math/HashCombine.h> +#include <renderengine/DisplaySettings.h> +#include <renderengine/RenderEngine.h> + +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<std::chrono::hours>(duration); + base::StringAppendF(&result, "%d hr ", static_cast<int>(hours.count())); + duration -= hours; + } + if (duration >= 1min) { + const auto minutes = std::chrono::duration_cast<std::chrono::minutes>(duration); + base::StringAppendF(&result, "%d min ", static_cast<int>(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<size_t>(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<size_t>(mBounds.width() * mBounds.height()); + + return creationCost; +} + +size_t CachedSet::getDisplayCost() const { + return static_cast<size_t>(mBounds.width() * mBounds.height()); +} + +bool CachedSet::hasBufferUpdate(std::vector<const LayerState*>::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> CachedSet::decompose() const { + std::vector<CachedSet> 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<renderengine::LayerSettings> 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<const renderengine::LayerSettings*> 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<float>(mBounds.getWidth()), + static_cast<float>(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<GraphicBuffer> buffer = new GraphicBuffer(static_cast<uint32_t>(mBounds.getWidth()), + static_cast<uint32_t>(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<std::chrono::milliseconds>(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 <compositionengine/impl/planner/Flattener.h> +#include <compositionengine/impl/planner/LayerState.h> +#include <compositionengine/impl/planner/Predictor.h> + +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<const LayerState*>& 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<size_t>(mDisplaySize.width * mDisplaySize.height); + base::StringAppendF(&result, " Unflattened: %.2f\n", + static_cast<float>(mUnflattenedDisplayCost) / displayArea); + base::StringAppendF(&result, " Flattened: %.2f\n", + static_cast<float>(mFlattenedDisplayCost) / displayArea); + + const auto compareLayerCounts = [](const std::pair<size_t, size_t>& left, + const std::pair<size_t, size_t>& 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<float>(mCachedSetCreationCost) / displayArea); + + const auto lastUpdate = + std::chrono::duration_cast<std::chrono::milliseconds>(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<const LayerState*>& 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<size_t>(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<size_t>(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<const LayerState*>& layers, time_point now) { + std::vector<CachedSet> 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<CachedSet>::const_iterator start, size_t length) + : start(start), length(length) {} + + std::vector<CachedSet>::const_iterator start; + size_t length; + }; + + if (mLayers.empty()) { + ALOGV("[%s] No layers found, returning", __func__); + return; + } + + std::vector<Run> 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 30f48922b6..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 <compositionengine/LayerFECompositionState.h> +#include <compositionengine/impl/OutputLayerCompositionState.h> #include <compositionengine/impl/planner/Planner.h> namespace android::compositionengine::impl::planner { +void Planner::setDisplaySize(ui::Size size) { + mFlattener.setDisplaySize(size); +} + void Planner::plan( compositionengine::Output::OutputLayersEnumerator<compositionengine::Output>&& layers) { std::unordered_set<LayerId> removedLayers; @@ -32,13 +38,16 @@ void Planner::plan( std::inserter(removedLayers, removedLayers.begin()), [](const auto& layer) { return layer.first; }); + std::vector<LayerId> currentLayerIds; for (auto layer : layers) { LayerId id = layer->getLayerFE().getSequence(); if (const auto layerEntry = mPreviousLayers.find(id); layerEntry != mPreviousLayers.end()) { // Track changes from previous info LayerState& state = layerEntry->second; Flags<LayerStateField> 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()); @@ -54,6 +63,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 +78,184 @@ 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) { + 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<const LayerState*>() + : mCurrentLayers, + mFlattenedHash); + 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<compositionengine::Output>&& 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(); + + finalPlan.addLayerType( + forcedOrRequestedClient + ? hardware::graphics::composer::hal::Composition::CLIENT + : layer->getLayerFE().getCompositionState()->compositionType); + } + + mPredictor.recordResult(mPredictedPlan, mFlattenedHash, mCurrentLayers, hasSkippedLayers, + finalPlan); +} + +void Planner::renderCachedSets(renderengine::RenderEngine& renderEngine) { + mFlattener.renderCachedSets(renderEngine); +} + +void Planner::dump(const Vector<String16>& 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 " + "<left_hash> <right_hash>'\n", + command.string()); + return; + } + if (args.size() > 4) { + base::StringAppendF(&result, + "Too many arguments found, expected '--planner %s <left_hash> " + "<right_hash>'\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 <hash>'\n", + command.string()); + return; + } + if (args.size() > 3) { + base::StringAppendF(&result, + "Too many arguments found, expected '--planner %s <hash>'\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 <plan>'\n", + command.string()); + return; + } + if (args.size() > 3) { + base::StringAppendF(&result, + "Too many arguments found, expected '--planner %s <plan>'\n", + command.string()); + return; + } + + const String8 planString(args[2]); + std::optional<Plan> 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 + + mFlattener.dump(result); + result.append("\n"); + + mPredictor.dump(result); } -void Planner::dump(const Vector<String16>& /* 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 <command> [arguments]\n\n"); + + result.append("If run without a command, dumps current Planner state\n\n"); + + result.append("Commands:\n"); + + result.append("[--compare|-c] <left_hash> <right_hash>\n"); + result.append(" Compares the predictions <left_hash> and <right_hash> by showing differences" + " in their example layer stacks\n"); + + result.append("[--describe|-d] <hash>\n"); + result.append(" Prints the example layer stack and prediction statistics for <hash>\n"); + + result.append("[--help|-h]\n"); + result.append(" Shows this message\n"); + + result.append("[--similar|-s] <plan>\n"); + result.append(" Prints the example layer names for similar stacks matching <plan>\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..ba5e64dec6 --- /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 <compositionengine/impl/planner/Predictor.h> + +namespace android::compositionengine::impl::planner { + +std::optional<LayerStack::ApproximateMatch> LayerStack::getApproximateMatch( + const std::vector<const LayerState*>& other) const { + // Differing numbers of layers are never an approximate match + if (mLayers.size() != other.size()) { + return std::nullopt; + } + + std::optional<ApproximateMatch> 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<LayerStateField> 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> 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::PredictedPlan> Predictor::getPredictedPlan( + const std::vector<const LayerState*>& layers, NonBufferHash hash) const { + // First check for an exact match + if (std::optional<Plan> 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<NonBufferHash> 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> predictedPlan, + NonBufferHash flattenedHash, + const std::vector<const LayerState*>& layers, bool hasSkippedLayers, + Plan result) { + if (predictedPlan) { + recordPredictedResult(*predictedPlan, layers, std::move(result)); + return; + } + + ++mMissCount; + + if (!hasSkippedLayers && findSimilarPrediction(layers, result)) { + return; + } + + ALOGV("[%s] Adding novel candidate %zx", __func__, flattenedHash); + mCandidates.emplace_front(flattenedHash, 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<Prediction&>(const_cast<const Predictor*>(this)->getPrediction(hash)); +} + +std::optional<Plan> 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<NonBufferHash> Predictor::getApproximateMatch( + const std::vector<const LayerState*>& 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<const LayerState*>& 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<const LayerState*>& layers, Plan result) { + const auto stacksEntry = mSimilarStacks.find(result); + if (stacksEntry == mSimilarStacks.end()) { + return false; + } + + std::optional<ApproximateStack> 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<HashFrequency> 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 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<uint8_t> 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 f654c2fd07..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); @@ -891,6 +900,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(); @@ -1645,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<OutputPartialMock> mOutput; @@ -1664,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); } @@ -3479,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<LayerFE::LayerSettings>{})); EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, false, _, _)).WillOnce(Return(NO_ERROR)); @@ -4060,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; @@ -4089,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); |