Merge "Add blur support in caching" into sc-dev
diff --git a/libs/renderengine/include/renderengine/LayerSettings.h b/libs/renderengine/include/renderengine/LayerSettings.h
index bfb7465..ff4d112 100644
--- a/libs/renderengine/include/renderengine/LayerSettings.h
+++ b/libs/renderengine/include/renderengine/LayerSettings.h
@@ -194,18 +194,6 @@
lhs.length == rhs.length && lhs.casterIsTranslucent == rhs.casterIsTranslucent;
}
-static inline bool operator==(const BlurRegion& lhs, const BlurRegion& rhs) {
- return lhs.alpha == rhs.alpha && lhs.cornerRadiusTL == rhs.cornerRadiusTL &&
- lhs.cornerRadiusTR == rhs.cornerRadiusTR && lhs.cornerRadiusBL == rhs.cornerRadiusBL &&
- lhs.cornerRadiusBR == rhs.cornerRadiusBR && lhs.blurRadius == rhs.blurRadius &&
- lhs.left == rhs.left && lhs.top == rhs.top && lhs.right == rhs.right &&
- lhs.bottom == rhs.bottom;
-}
-
-static inline bool operator!=(const BlurRegion& lhs, const BlurRegion& rhs) {
- return !(lhs == rhs);
-}
-
static inline bool operator==(const LayerSettings& lhs, const LayerSettings& rhs) {
if (lhs.blurRegions.size() != rhs.blurRegions.size()) {
return false;
diff --git a/libs/ui/include/ui/BlurRegion.h b/libs/ui/include/ui/BlurRegion.h
index 69a586e..a9ca369 100644
--- a/libs/ui/include/ui/BlurRegion.h
+++ b/libs/ui/include/ui/BlurRegion.h
@@ -20,6 +20,8 @@
#include <iosfwd>
#include <iostream>
+#include <math/HashCombine.h>
+
namespace android {
struct BlurRegion {
@@ -33,6 +35,16 @@
int top;
int right;
int bottom;
+
+ inline bool operator==(const BlurRegion& other) const {
+ return blurRadius == other.blurRadius && cornerRadiusTL == other.cornerRadiusTL &&
+ cornerRadiusTR == other.cornerRadiusTR && cornerRadiusBL == other.cornerRadiusBL &&
+ cornerRadiusBR == other.cornerRadiusBR && alpha == other.alpha &&
+ left == other.left && top == other.top && right == other.right &&
+ bottom == other.bottom;
+ }
+
+ inline bool operator!=(const BlurRegion& other) const { return !(*this == other); }
};
static inline void PrintTo(const BlurRegion& blurRegion, ::std::ostream* os) {
@@ -50,4 +62,15 @@
*os << "\n}";
}
-} // namespace android
\ No newline at end of file
+} // namespace android
+
+namespace std {
+template <>
+struct hash<android::BlurRegion> {
+ size_t operator()(const android::BlurRegion& region) const {
+ return android::hashCombine(region.blurRadius, region.cornerRadiusTL, region.cornerRadiusTR,
+ region.cornerRadiusBL, region.cornerRadiusBR, region.alpha,
+ region.left, region.top, region.right, region.bottom);
+ }
+};
+} // namespace std
\ No newline at end of file
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h
index 06f26eb..dc6eab4 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h
@@ -110,6 +110,9 @@
// CachedSet and punching a hole.
bool requiresHolePunch() const;
+ // True if any constituent layer is configured to blur any layers behind.
+ bool hasBlurBehind() const;
+
// Add a layer that will be drawn behind this one. ::render() will render a
// hole in this CachedSet's buffer, allowing the supplied layer to peek
// through. Must be called before ::render().
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h
index 864251f..213c55e 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h
@@ -20,6 +20,7 @@
#include <compositionengine/impl/planner/CachedSet.h>
#include <compositionengine/impl/planner/LayerState.h>
+#include <numeric>
#include <vector>
namespace android {
@@ -60,6 +61,73 @@
bool mergeWithCachedSets(const std::vector<const LayerState*>& layers,
std::chrono::steady_clock::time_point now);
+ // A Run is a sequence of CachedSets, which is a candidate for flattening into a single
+ // CachedSet. Because it is wasteful to flatten 1 CachedSet, a Run must contain more than 1
+ // CachedSet
+ class Run {
+ public:
+ // A builder for a Run, to aid in construction
+ class Builder {
+ private:
+ std::vector<CachedSet>::const_iterator mStart;
+ std::vector<size_t> mLengths;
+ const CachedSet* mHolePunchCandidate = nullptr;
+
+ public:
+ // Initializes a Builder a CachedSet to start from.
+ // This start iterator must be an iterator for mLayers
+ void init(const std::vector<CachedSet>::const_iterator& start) {
+ mStart = start;
+ mLengths.push_back(start->getLayerCount());
+ }
+
+ // Appends a new CachedSet to the end of the run
+ // The provided length must be the size of the next sequential CachedSet in layers
+ void append(size_t length) { mLengths.push_back(length); }
+
+ // Sets the hole punch candidate for the Run.
+ void setHolePunchCandidate(const CachedSet* holePunchCandidate) {
+ mHolePunchCandidate = holePunchCandidate;
+ }
+
+ // Builds a Run instance, if a valid Run may be built.
+ std::optional<Run> validateAndBuild() {
+ if (mLengths.size() <= 1) {
+ return std::nullopt;
+ }
+
+ return Run(mStart,
+ std::reduce(mLengths.cbegin(), mLengths.cend(), 0u,
+ [](size_t left, size_t right) { return left + right; }),
+ mHolePunchCandidate);
+ }
+
+ void reset() { *this = {}; }
+ };
+
+ // Gets the starting CachedSet of this run.
+ // This is an iterator into mLayers
+ const std::vector<CachedSet>::const_iterator& getStart() const { return mStart; }
+ // Gets the total number of layers encompassing this Run.
+ size_t getLayerLength() const { return mLength; }
+ // Gets the hole punch candidate for this Run.
+ const CachedSet* getHolePunchCandidate() const { return mHolePunchCandidate; }
+
+ private:
+ Run(std::vector<CachedSet>::const_iterator start, size_t length,
+ const CachedSet* holePunchCandidate)
+ : mStart(start), mLength(length), mHolePunchCandidate(holePunchCandidate) {}
+ const std::vector<CachedSet>::const_iterator mStart;
+ const size_t mLength;
+ const CachedSet* const mHolePunchCandidate;
+
+ friend class Builder;
+ };
+
+ std::vector<Run> findCandidateRuns(std::chrono::steady_clock::time_point now) const;
+
+ std::optional<Run> findBestRun(std::vector<Run>& runs) const;
+
void buildCachedSets(std::chrono::steady_clock::time_point now);
const bool mEnableHolePunch;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h
index 3391273..fef0dfb 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h
@@ -26,6 +26,7 @@
#include <string>
#include "DisplayHardware/Hal.h"
+#include "math/HashCombine.h"
namespace std {
template <typename T>
@@ -48,24 +49,26 @@
// clang-format off
enum class LayerStateField : uint32_t {
- None = 0u,
- Id = 1u << 0,
- Name = 1u << 1,
- DisplayFrame = 1u << 2,
- SourceCrop = 1u << 3,
- BufferTransform = 1u << 4,
- BlendMode = 1u << 5,
- Alpha = 1u << 6,
- LayerMetadata = 1u << 7,
- VisibleRegion = 1u << 8,
- Dataspace = 1u << 9,
- PixelFormat = 1u << 10,
- ColorTransform = 1u << 11,
- SurfaceDamage = 1u << 12,
- CompositionType = 1u << 13,
- SidebandStream = 1u << 14,
- Buffer = 1u << 15,
- SolidColor = 1u << 16,
+ None = 0u,
+ Id = 1u << 0,
+ Name = 1u << 1,
+ DisplayFrame = 1u << 2,
+ SourceCrop = 1u << 3,
+ BufferTransform = 1u << 4,
+ BlendMode = 1u << 5,
+ Alpha = 1u << 6,
+ LayerMetadata = 1u << 7,
+ VisibleRegion = 1u << 8,
+ Dataspace = 1u << 9,
+ PixelFormat = 1u << 10,
+ ColorTransform = 1u << 11,
+ SurfaceDamage = 1u << 12,
+ CompositionType = 1u << 13,
+ SidebandStream = 1u << 14,
+ Buffer = 1u << 15,
+ SolidColor = 1u << 16,
+ BackgroundBlurRadius = 1u << 17,
+ BlurRegions = 1u << 18,
};
// clang-format on
@@ -225,6 +228,9 @@
const std::string& getName() const { return mName.get(); }
Rect getDisplayFrame() const { return mDisplayFrame.get(); }
const Region& getVisibleRegion() const { return mVisibleRegion.get(); }
+ bool hasBlurBehind() const {
+ return mBackgroundBlurRadius.get() > 0 || !mBlurRegions.get().empty();
+ }
hardware::graphics::composer::hal::Composition getCompositionType() const {
return mCompositionType.get();
}
@@ -398,7 +404,45 @@
return std::vector<std::string>{stream.str()};
}};
- static const constexpr size_t kNumNonUniqueFields = 14;
+ OutputLayerState<int32_t, LayerStateField::BackgroundBlurRadius> mBackgroundBlurRadius{
+ [](auto layer) {
+ return layer->getLayerFE().getCompositionState()->backgroundBlurRadius;
+ }};
+
+ using BlurRegionsState =
+ OutputLayerState<std::vector<BlurRegion>, LayerStateField::BlurRegions>;
+ BlurRegionsState mBlurRegions{[](auto layer) {
+ return layer->getLayerFE().getCompositionState()->blurRegions;
+ },
+ [](const std::vector<BlurRegion>& regions) {
+ std::vector<std::string> result;
+ for (const auto region : regions) {
+ std::string str;
+ base::StringAppendF(&str,
+ "{radius=%du, cornerRadii=[%f, %f, "
+ "%f, %f], alpha=%f, rect=[%d, "
+ "%d, %d, %d]",
+ region.blurRadius,
+ region.cornerRadiusTL,
+ region.cornerRadiusTR,
+ region.cornerRadiusBL,
+ region.cornerRadiusBR, region.alpha,
+ region.left, region.top, region.right,
+ region.bottom);
+ result.push_back(str);
+ }
+ return result;
+ },
+ BlurRegionsState::getDefaultEquals(),
+ [](const std::vector<BlurRegion>& regions) {
+ size_t hash = 0;
+ for (const auto& region : regions) {
+ android::hashCombineSingle(hash, region);
+ }
+ return hash;
+ }};
+
+ static const constexpr size_t kNumNonUniqueFields = 16;
std::array<StateInterface*, kNumNonUniqueFields> getNonUniqueFields() {
std::array<const StateInterface*, kNumNonUniqueFields> constFields =
@@ -413,10 +457,10 @@
std::array<const StateInterface*, kNumNonUniqueFields> getNonUniqueFields() const {
return {
- &mDisplayFrame, &mSourceCrop, &mBufferTransform, &mBlendMode,
- &mAlpha, &mLayerMetadata, &mVisibleRegion, &mOutputDataspace,
- &mPixelFormat, &mColorTransform, &mCompositionType, &mSidebandStream,
- &mBuffer, &mSolidColor,
+ &mDisplayFrame, &mSourceCrop, &mBufferTransform, &mBlendMode,
+ &mAlpha, &mLayerMetadata, &mVisibleRegion, &mOutputDataspace,
+ &mPixelFormat, &mColorTransform, &mCompositionType, &mSidebandStream,
+ &mBuffer, &mSolidColor, &mBackgroundBlurRadius, &mBlurRegions,
};
}
};
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
index 67854cf..63085cc 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
@@ -280,6 +280,11 @@
return layerFE.hasRoundedCorners();
}
+bool CachedSet::hasBlurBehind() const {
+ return std::any_of(mLayers.cbegin(), mLayers.cend(),
+ [](const Layer& layer) { return layer.getState()->hasBlurBehind(); });
+}
+
namespace {
bool contains(const Rect& outer, const Rect& inner) {
return outer.left <= inner.left && outer.right >= inner.right && outer.top <= inner.top &&
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
index 4453a99..294ec4b 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
@@ -327,85 +327,94 @@
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<Flattener::Run> Flattener::findCandidateRuns(time_point now) const {
std::vector<Run> runs;
bool isPartOfRun = false;
-
- // Keep track of the layer that follows a run. It's possible that we will
- // render it with a hole-punch.
- const CachedSet* holePunchLayer = nullptr;
+ Run::Builder builder;
+ bool firstLayer = true;
+ bool runHasFirstLayer = false;
for (auto currentSet = mLayers.cbegin(); currentSet != mLayers.cend(); ++currentSet) {
- if (now - currentSet->getLastUpdate() > kActiveLayerTimeout) {
- // Layer is inactive
+ const bool layerIsInactive = now - currentSet->getLastUpdate() > kActiveLayerTimeout;
+ const bool layerHasBlur = currentSet->hasBlurBehind();
+ if (layerIsInactive && (firstLayer || runHasFirstLayer || !layerHasBlur)) {
if (isPartOfRun) {
- runs.back().length += currentSet->getLayerCount();
+ builder.append(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());
+ builder.init(currentSet);
+ if (firstLayer) {
+ runHasFirstLayer = true;
+ }
isPartOfRun = true;
}
}
} else if (isPartOfRun) {
- // Runs must be at least 2 sets long or there's nothing to combine
- if (runs.back().start->getLayerCount() == runs.back().length) {
- runs.pop_back();
- } else {
- // The prior run contained at least two sets. Currently, we'll
- // only possibly merge a single run, so only keep track of a
- // holePunchLayer if this is the first run.
- if (runs.size() == 1) {
- holePunchLayer = &(*currentSet);
- }
-
- // TODO(b/185114532: Break out of the loop? We may find more runs, but we
- // won't do anything with them.
+ builder.setHolePunchCandidate(&(*currentSet));
+ if (auto run = builder.validateAndBuild(); run) {
+ runs.push_back(*run);
}
+ runHasFirstLayer = false;
+ builder.reset();
isPartOfRun = false;
}
+
+ firstLayer = 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();
+ // If we're in the middle of a run at the end, we still need to validate and build it.
+ if (isPartOfRun) {
+ if (auto run = builder.validateAndBuild(); run) {
+ runs.push_back(*run);
+ }
}
ALOGV("[%s] Found %zu candidate runs", __func__, runs.size());
+ return runs;
+}
+
+std::optional<Flattener::Run> Flattener::findBestRun(std::vector<Flattener::Run>& runs) const {
if (runs.empty()) {
+ return std::nullopt;
+ }
+
+ // TODO (b/181192467): Choose the best run, instead of just the first.
+ return runs[0];
+}
+
+void Flattener::buildCachedSets(time_point now) {
+ if (mLayers.empty()) {
+ ALOGV("[%s] No layers found, returning", __func__);
return;
}
- mNewCachedSet.emplace(*runs[0].start);
+ std::vector<Run> runs = findCandidateRuns(now);
+
+ std::optional<Run> bestRun = findBestRun(runs);
+
+ if (!bestRun) {
+ return;
+ }
+
+ mNewCachedSet.emplace(*bestRun->getStart());
mNewCachedSet->setLastUpdate(now);
- auto currentSet = runs[0].start;
- while (mNewCachedSet->getLayerCount() < runs[0].length) {
+ auto currentSet = bestRun->getStart();
+ while (mNewCachedSet->getLayerCount() < bestRun->getLayerLength()) {
++currentSet;
mNewCachedSet->append(*currentSet);
}
- if (mEnableHolePunch && holePunchLayer && holePunchLayer->requiresHolePunch()) {
+ if (mEnableHolePunch && bestRun->getHolePunchCandidate() &&
+ bestRun->getHolePunchCandidate()->requiresHolePunch()) {
// Add the pip layer to mNewCachedSet, but in a special way - it should
// replace the buffer with a clear round rect.
- mNewCachedSet->addHolePunchLayerIfFeasible(*holePunchLayer,
- runs[0].start == mLayers.cbegin());
+ mNewCachedSet->addHolePunchLayerIfFeasible(*bestRun->getHolePunchCandidate(),
+ bestRun->getStart() == mLayers.cbegin());
}
// TODO(b/181192467): Actually compute new LayerState vector and corresponding hash for each run
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp
index a39331c..488f64d 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp
@@ -567,5 +567,30 @@
}
}
+TEST_F(CachedSetTest, hasBlurBehind) {
+ mTestLayers[1]->layerFECompositionState.backgroundBlurRadius = 1;
+ mTestLayers[1]->layerState->update(&mTestLayers[1]->outputLayer);
+ mTestLayers[2]->layerFECompositionState.blurRegions.push_back(
+ BlurRegion{1, 0, 0, 0, 0, 0, 0, 0, 0, 0});
+ mTestLayers[2]->layerState->update(&mTestLayers[2]->outputLayer);
+
+ CachedSet::Layer& layer1 = *mTestLayers[0]->cachedSetLayer.get();
+ CachedSet::Layer& layer2 = *mTestLayers[1]->cachedSetLayer.get();
+ CachedSet::Layer& layer3 = *mTestLayers[2]->cachedSetLayer.get();
+
+ CachedSet cachedSet1(layer1);
+ CachedSet cachedSet2(layer2);
+ CachedSet cachedSet3(layer3);
+
+ // Cached set 4 will consist of layers 1 and 2, which will contain a blur behind
+ CachedSet cachedSet4(layer1);
+ cachedSet4.addLayer(layer2.getState(), kStartTime);
+
+ EXPECT_FALSE(cachedSet1.hasBlurBehind());
+ EXPECT_TRUE(cachedSet2.hasBlurBehind());
+ EXPECT_TRUE(cachedSet3.hasBlurBehind());
+ EXPECT_TRUE(cachedSet4.hasBlurBehind());
+}
+
} // namespace
} // namespace android::compositionengine
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
index 25fab49..42096f3 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
@@ -642,5 +642,149 @@
EXPECT_EQ(&mTestLayers[2]->outputLayer, peekThroughLayer1);
EXPECT_EQ(peekThroughLayer1, peekThroughLayer2);
}
+
+TEST_F(FlattenerTest, flattenLayers_flattensBlurBehindRunIfFirstRun) {
+ auto& layerState1 = mTestLayers[0]->layerState;
+
+ auto& layerState2 = mTestLayers[1]->layerState;
+ mTestLayers[1]->layerFECompositionState.backgroundBlurRadius = 1;
+ layerState2->update(&mTestLayers[1]->outputLayer);
+
+ auto& layerState3 = mTestLayers[2]->layerState;
+ const auto& overrideBuffer1 = layerState1->getOutputLayer()->getState().overrideInfo.buffer;
+ const auto& overrideBuffer2 = layerState2->getOutputLayer()->getState().overrideInfo.buffer;
+ const auto& overrideBuffer3 = layerState3->getOutputLayer()->getState().overrideInfo.buffer;
+
+ const std::vector<const LayerState*> layers = {
+ layerState1.get(),
+ layerState2.get(),
+ layerState3.get(),
+ };
+
+ initializeFlattener(layers);
+
+ // Mark the first two layers inactive, which contain the blur behind
+ mTime += 200ms;
+ layerState3->resetFramesSinceBufferUpdate();
+
+ // layers would be flattened but the buffer would not be overridden
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).WillOnce(Return(NO_ERROR));
+
+ initializeOverrideBuffer(layers);
+ EXPECT_EQ(getNonBufferHash(layers),
+ mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
+ mFlattener->renderCachedSets(mRenderEngine, mOutputState);
+
+ for (const auto layer : layers) {
+ EXPECT_EQ(nullptr, layer->getOutputLayer()->getState().overrideInfo.buffer);
+ }
+
+ // the new flattened layer is replaced
+ initializeOverrideBuffer(layers);
+ EXPECT_NE(getNonBufferHash(layers),
+ mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
+ mFlattener->renderCachedSets(mRenderEngine, mOutputState);
+ EXPECT_NE(nullptr, overrideBuffer1);
+ EXPECT_EQ(overrideBuffer1, overrideBuffer2);
+ EXPECT_EQ(nullptr, overrideBuffer3);
+}
+
+TEST_F(FlattenerTest, flattenLayers_doesNotFlattenBlurBehindRun) {
+ auto& layerState1 = mTestLayers[0]->layerState;
+
+ auto& layerState2 = mTestLayers[1]->layerState;
+ mTestLayers[1]->layerFECompositionState.backgroundBlurRadius = 1;
+ layerState2->update(&mTestLayers[1]->outputLayer);
+
+ auto& layerState3 = mTestLayers[2]->layerState;
+
+ const std::vector<const LayerState*> layers = {
+ layerState1.get(),
+ layerState2.get(),
+ layerState3.get(),
+ };
+
+ initializeFlattener(layers);
+
+ // Mark the last two layers inactive, which contains the blur layer, but does not contain the
+ // first layer
+ mTime += 200ms;
+ layerState1->resetFramesSinceBufferUpdate();
+
+ // layers would be flattened but the buffer would not be overridden
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).WillRepeatedly(Return(NO_ERROR));
+
+ initializeOverrideBuffer(layers);
+ EXPECT_EQ(getNonBufferHash(layers),
+ mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
+ mFlattener->renderCachedSets(mRenderEngine, mOutputState);
+
+ for (const auto layer : layers) {
+ EXPECT_EQ(nullptr, layer->getOutputLayer()->getState().overrideInfo.buffer);
+ }
+
+ // nothing is flattened because the last two frames cannot be cached due to containing a blur
+ // layer
+ initializeOverrideBuffer(layers);
+ EXPECT_EQ(getNonBufferHash(layers),
+ mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
+ mFlattener->renderCachedSets(mRenderEngine, mOutputState);
+ for (const auto layer : layers) {
+ EXPECT_EQ(nullptr, layer->getOutputLayer()->getState().overrideInfo.buffer);
+ }
+}
+
+TEST_F(FlattenerTest, flattenLayers_flattenSkipsLayerWithBlurBehind) {
+ auto& layerState1 = mTestLayers[0]->layerState;
+
+ auto& layerStateWithBlurBehind = mTestLayers[1]->layerState;
+ mTestLayers[1]->layerFECompositionState.backgroundBlurRadius = 1;
+ layerStateWithBlurBehind->update(&mTestLayers[1]->outputLayer);
+
+ auto& layerState3 = mTestLayers[2]->layerState;
+ auto& layerState4 = mTestLayers[3]->layerState;
+ const auto& overrideBuffer1 = layerState1->getOutputLayer()->getState().overrideInfo.buffer;
+ const auto& blurOverrideBuffer =
+ layerStateWithBlurBehind->getOutputLayer()->getState().overrideInfo.buffer;
+ const auto& overrideBuffer3 = layerState3->getOutputLayer()->getState().overrideInfo.buffer;
+ const auto& overrideBuffer4 = layerState4->getOutputLayer()->getState().overrideInfo.buffer;
+
+ const std::vector<const LayerState*> layers = {
+ layerState1.get(),
+ layerStateWithBlurBehind.get(),
+ layerState3.get(),
+ layerState4.get(),
+ };
+
+ initializeFlattener(layers);
+
+ // Mark the last three layers inactive, which contains the blur layer, but does not contain the
+ // first layer
+ mTime += 200ms;
+ layerState1->resetFramesSinceBufferUpdate();
+
+ // layers would be flattened but the buffer would not be overridden
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).WillOnce(Return(NO_ERROR));
+
+ initializeOverrideBuffer(layers);
+ EXPECT_EQ(getNonBufferHash(layers),
+ mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
+ mFlattener->renderCachedSets(mRenderEngine, mOutputState);
+
+ for (const auto layer : layers) {
+ EXPECT_EQ(nullptr, layer->getOutputLayer()->getState().overrideInfo.buffer);
+ }
+
+ // the new flattened layer is replaced
+ initializeOverrideBuffer(layers);
+ EXPECT_NE(getNonBufferHash(layers),
+ mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
+ mFlattener->renderCachedSets(mRenderEngine, mOutputState);
+ EXPECT_EQ(nullptr, overrideBuffer1);
+ EXPECT_EQ(nullptr, blurOverrideBuffer);
+ EXPECT_NE(nullptr, overrideBuffer3);
+ EXPECT_EQ(overrideBuffer3, overrideBuffer4);
+}
+
} // namespace
} // namespace android::compositionengine
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp
index 948c850..a09ce14 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp
@@ -58,6 +58,10 @@
const GenericLayerMetadataEntry sMetadataValueTwo = GenericLayerMetadataEntry{
.value = std::vector<uint8_t>({1, 3}),
};
+const constexpr int32_t sBgBlurRadiusOne = 3;
+const constexpr int32_t sBgBlurRadiusTwo = 4;
+const BlurRegion sBlurRegionOne = BlurRegion{1, 2.f, 3.f, 4.f, 5.f, 6.f, 7, 8, 9, 10};
+const BlurRegion sBlurRegionTwo = BlurRegion{2, 3.f, 4.f, 5.f, 6.f, 7.f, 8, 9, 10, 11};
struct LayerStateTest : public testing::Test {
LayerStateTest() {
@@ -830,6 +834,114 @@
EXPECT_TRUE(otherLayerState->compare(*mLayerState));
}
+TEST_F(LayerStateTest, updateBackgroundBlur) {
+ OutputLayerCompositionState outputLayerCompositionState;
+ LayerFECompositionState layerFECompositionState;
+ layerFECompositionState.backgroundBlurRadius = sBgBlurRadiusOne;
+ setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+ layerFECompositionState);
+ mLayerState = std::make_unique<LayerState>(&mOutputLayer);
+
+ mock::OutputLayer newOutputLayer;
+ mock::LayerFE newLayerFE;
+ LayerFECompositionState layerFECompositionStateTwo;
+ layerFECompositionStateTwo.backgroundBlurRadius = sBgBlurRadiusTwo;
+ setupMocksForLayer(newOutputLayer, newLayerFE, outputLayerCompositionState,
+ layerFECompositionStateTwo);
+ Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
+ EXPECT_EQ(Flags<LayerStateField>(LayerStateField::BackgroundBlurRadius), updates);
+}
+
+TEST_F(LayerStateTest, compareBackgroundBlur) {
+ OutputLayerCompositionState outputLayerCompositionState;
+ LayerFECompositionState layerFECompositionState;
+ layerFECompositionState.backgroundBlurRadius = sBgBlurRadiusOne;
+ setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+ layerFECompositionState);
+ mLayerState = std::make_unique<LayerState>(&mOutputLayer);
+ mock::OutputLayer newOutputLayer;
+ mock::LayerFE newLayerFE;
+ LayerFECompositionState layerFECompositionStateTwo;
+ layerFECompositionStateTwo.backgroundBlurRadius = sBgBlurRadiusTwo;
+ setupMocksForLayer(newOutputLayer, newLayerFE, outputLayerCompositionState,
+ layerFECompositionStateTwo);
+ auto otherLayerState = std::make_unique<LayerState>(&newOutputLayer);
+
+ verifyNonUniqueDifferingFields(*mLayerState, *otherLayerState,
+ LayerStateField::BackgroundBlurRadius);
+
+ EXPECT_TRUE(mLayerState->compare(*otherLayerState));
+ EXPECT_TRUE(otherLayerState->compare(*mLayerState));
+}
+
+TEST_F(LayerStateTest, updateBlurRegions) {
+ OutputLayerCompositionState outputLayerCompositionState;
+ LayerFECompositionState layerFECompositionState;
+ layerFECompositionState.blurRegions.push_back(sBlurRegionOne);
+ setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+ layerFECompositionState);
+ mLayerState = std::make_unique<LayerState>(&mOutputLayer);
+
+ mock::OutputLayer newOutputLayer;
+ mock::LayerFE newLayerFE;
+ LayerFECompositionState layerFECompositionStateTwo;
+ layerFECompositionStateTwo.blurRegions.push_back(sBlurRegionTwo);
+ setupMocksForLayer(newOutputLayer, newLayerFE, outputLayerCompositionState,
+ layerFECompositionStateTwo);
+ Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
+ EXPECT_EQ(Flags<LayerStateField>(LayerStateField::BlurRegions), updates);
+}
+
+TEST_F(LayerStateTest, compareBlurRegions) {
+ OutputLayerCompositionState outputLayerCompositionState;
+ LayerFECompositionState layerFECompositionState;
+ layerFECompositionState.blurRegions.push_back(sBlurRegionOne);
+ setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+ layerFECompositionState);
+ mLayerState = std::make_unique<LayerState>(&mOutputLayer);
+ mock::OutputLayer newOutputLayer;
+ mock::LayerFE newLayerFE;
+ LayerFECompositionState layerFECompositionStateTwo;
+ layerFECompositionStateTwo.blurRegions.push_back(sBlurRegionTwo);
+ setupMocksForLayer(newOutputLayer, newLayerFE, outputLayerCompositionState,
+ layerFECompositionStateTwo);
+ auto otherLayerState = std::make_unique<LayerState>(&newOutputLayer);
+
+ verifyNonUniqueDifferingFields(*mLayerState, *otherLayerState, LayerStateField::BlurRegions);
+
+ EXPECT_TRUE(mLayerState->compare(*otherLayerState));
+ EXPECT_TRUE(otherLayerState->compare(*mLayerState));
+}
+
+TEST_F(LayerStateTest, hasBlurBehind_noBlur_returnsFalse) {
+ OutputLayerCompositionState outputLayerCompositionState;
+ LayerFECompositionState layerFECompositionState;
+ setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+ layerFECompositionState);
+ mLayerState = std::make_unique<LayerState>(&mOutputLayer);
+ EXPECT_FALSE(mLayerState->hasBlurBehind());
+}
+
+TEST_F(LayerStateTest, hasBlurBehind_withBackgroundBlur_returnsTrue) {
+ OutputLayerCompositionState outputLayerCompositionState;
+ LayerFECompositionState layerFECompositionState;
+ layerFECompositionState.backgroundBlurRadius = sBgBlurRadiusOne;
+ setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+ layerFECompositionState);
+ mLayerState = std::make_unique<LayerState>(&mOutputLayer);
+ EXPECT_TRUE(mLayerState->hasBlurBehind());
+}
+
+TEST_F(LayerStateTest, hasBlurBehind_withBlurRegion_returnsTrue) {
+ OutputLayerCompositionState outputLayerCompositionState;
+ LayerFECompositionState layerFECompositionState;
+ layerFECompositionState.blurRegions.push_back(sBlurRegionOne);
+ setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+ layerFECompositionState);
+ mLayerState = std::make_unique<LayerState>(&mOutputLayer);
+ EXPECT_TRUE(mLayerState->hasBlurBehind());
+}
+
TEST_F(LayerStateTest, dumpDoesNotCrash) {
OutputLayerCompositionState outputLayerCompositionState;
LayerFECompositionState layerFECompositionState;