SF: Add Planner flattener

Adds the Planner flattener, which detects when layers are inactive and
flattens them using GPU composition in order to reduce the workload on
the display processor.

Bug: 158790260
Test: atest libcompositionengine_test libsurfaceflinger_unittest
Change-Id: Idc5c2927c8af6fe85653404a7d94c9e68ffc329b
diff --git a/services/surfaceflinger/CompositionEngine/Android.bp b/services/surfaceflinger/CompositionEngine/Android.bp
index e2f2324..f9e5b9a 100644
--- a/services/surfaceflinger/CompositionEngine/Android.bp
+++ b/services/surfaceflinger/CompositionEngine/Android.bp
@@ -52,6 +52,8 @@
     name: "libcompositionengine",
     defaults: ["libcompositionengine_defaults"],
     srcs: [
+        "src/planner/CachedSet.cpp",
+        "src/planner/Flattener.cpp",
         "src/planner/LayerState.cpp",
         "src/planner/Planner.cpp",
         "src/planner/Predictor.cpp",
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
index a8ecb62..4976213 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
@@ -280,6 +280,7 @@
     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 fb19216..3a84327 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 @@
 
 class CompositionEngine;
 class Output;
-class LayerFE;
 
 namespace impl {
 struct OutputLayerCompositionState;
@@ -88,8 +89,9 @@
 
     // 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 @@
     // 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 044ddd8..eeb20fc 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
@@ -94,6 +94,7 @@
     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
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h
index 8cb5ae8..f113c34 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 @@
 
     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 @@
     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 @@
 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 9a118d3..5f834be 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h
@@ -84,6 +84,13 @@
     // 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 0000000..00424b2
--- /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 0000000..6c86408
--- /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 ad53d27..e96abb7 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Planner.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Planner.h
@@ -17,6 +17,7 @@
 #pragma once
 
 #include <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>
@@ -28,6 +29,10 @@
 
 namespace android {
 
+namespace renderengine {
+class RenderEngine;
+} // namespace renderengine
+
 namespace compositionengine::impl::planner {
 
 // This is the top level class for layer caching. It is responsible for
@@ -36,8 +41,15 @@
 // 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);
 
@@ -46,6 +58,9 @@
     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:
@@ -56,8 +71,10 @@
     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
index 38c73f7..422af77 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Predictor.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Predictor.h
@@ -210,8 +210,8 @@
     std::optional<PredictedPlan> getPredictedPlan(const std::vector<const LayerState*>&,
                                                   NonBufferHash) const;
 
-    void recordResult(std::optional<PredictedPlan> predictedPlan,
-                      const std::vector<const LayerState*>&, Plan result);
+    void recordResult(std::optional<PredictedPlan> predictedPlan, NonBufferHash flattenedHash,
+                      const std::vector<const LayerState*>&, bool hasSkippedLayers, Plan result);
 
     void dump(std::string&) const;
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
index 9299199..5aa53e5 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
@@ -107,6 +107,7 @@
     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 81e1fc7..2454ff7 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/OutputLayer.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/OutputLayer.h
@@ -39,7 +39,7 @@
     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 @@
     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 57b7481..dc1aacc 100644
--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -198,6 +198,10 @@
     const Rect newOrientedBounds(orientedSize);
     state.orientedDisplaySpace.bounds = newOrientedBounds;
 
+    if (mPlanner) {
+        mPlanner->setDisplaySize(size);
+    }
+
     dirtyEntireOutput();
 }
 
@@ -317,7 +321,11 @@
 
 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 @@
     devOptRepaintFlash(refreshArgs);
     finishFrame(refreshArgs);
     postFramebuffer();
+    renderCachedSets();
 }
 
 void Output::rebuildLayerStacks(const compositionengine::CompositionRefreshArgs& refreshArgs,
@@ -701,8 +710,23 @@
         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);
     }
 }
 
@@ -1126,10 +1150,16 @@
                                    .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(),
@@ -1220,6 +1250,12 @@
     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 0faab6f..54784a2 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::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 @@
 
     if (includeGeometry) {
         writeOutputDependentGeometryStateToHWC(hwcLayer.get(), requestedCompositionType);
-        writeOutputIndependentGeometryStateToHWC(hwcLayer.get(), *outputIndependentState);
+        writeOutputIndependentGeometryStateToHWC(hwcLayer.get(), *outputIndependentState,
+                                                 skipLayer);
     }
 
     writeOutputDependentPerFrameStateToHWC(hwcLayer.get());
@@ -352,23 +352,27 @@
         HWC2::Layer* hwcLayer, hal::Composition requestedCompositionType) {
     const auto& outputDependentState = getState();
 
-    if (auto error = hwcLayer->setDisplayFrame(outputDependentState.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));
+    Rect displayFrame = outputDependentState.displayFrame;
+    FloatRect sourceCrop = outputDependentState.sourceCrop;
+    if (outputDependentState.overrideInfo.buffer != nullptr) { // adyabr
+        displayFrame = outputDependentState.overrideInfo.displayFrame;
+        sourceCrop = displayFrame.toFloatRect();
     }
 
-    if (auto error = hwcLayer->setSourceCrop(outputDependentState.sourceCrop);
-        error != hal::Error::NONE) {
+    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(), displayFrame.left, displayFrame.top, displayFrame.right,
+              displayFrame.bottom, to_string(error).c_str(), static_cast<int32_t>(error));
+    }
+
+    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::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 @@
               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 @@
               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 @@
             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 0000000..ab3fe9e
--- /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 0000000..0c09714
--- /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 619d008..52efff5 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;
@@ -39,7 +45,9 @@
             // 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());
 
@@ -74,10 +82,21 @@
     mCurrentLayers.clear();
     mCurrentLayers.reserve(currentLayerIds.size());
     std::transform(currentLayerIds.cbegin(), currentLayerIds.cend(),
-                   std::back_inserter(mCurrentLayers),
-                   [this](LayerId id) { return &mPreviousLayers.at(id); });
+                   std::back_inserter(mCurrentLayers), [this](LayerId id) {
+                       LayerState* state = &mPreviousLayers.at(id);
+                       state->getOutputLayer()->editState().overrideInfo = {};
+                       return state;
+                   });
 
-    mPredictedPlan = mPredictor.getPredictedPlan(mCurrentLayers, getNonBufferHash(mCurrentLayers));
+    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 {
@@ -88,7 +107,18 @@
 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();
 
@@ -98,7 +128,12 @@
                         : layer->getLayerFE().getCompositionState()->compositionType);
     }
 
-    mPredictor.recordResult(mPredictedPlan, mCurrentLayers, finalPlan);
+    mPredictor.recordResult(mPredictedPlan, mFlattenedHash, mCurrentLayers, hasSkippedLayers,
+                            finalPlan);
+}
+
+void Planner::renderCachedSets(renderengine::RenderEngine& renderEngine) {
+    mFlattener.renderCachedSets(renderEngine);
 }
 
 void Planner::dump(const Vector<String16>& args, std::string& result) {
@@ -194,6 +229,10 @@
     }
 
     // If there are no specific commands, dump the usual state
+
+    mFlattener.dump(result);
+    result.append("\n");
+
     mPredictor.dump(result);
 }
 
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Predictor.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Predictor.cpp
index 28be9db..ba5e64d 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/Predictor.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/Predictor.cpp
@@ -176,7 +176,9 @@
 }
 
 void Predictor::recordResult(std::optional<PredictedPlan> predictedPlan,
-                             const std::vector<const LayerState*>& layers, Plan result) {
+                             NonBufferHash flattenedHash,
+                             const std::vector<const LayerState*>& layers, bool hasSkippedLayers,
+                             Plan result) {
     if (predictedPlan) {
         recordPredictedResult(*predictedPlan, layers, std::move(result));
         return;
@@ -184,14 +186,12 @@
 
     ++mMissCount;
 
-    if (findSimilarPrediction(layers, result)) {
+    if (!hasSkippedLayers && findSimilarPrediction(layers, result)) {
         return;
     }
 
-    const NonBufferHash hash = getNonBufferHash(layers);
-
-    ALOGV("[%s] Adding novel candidate %zx", __func__, hash);
-    mCandidates.emplace_front(hash, Prediction(layers, result));
+    ALOGV("[%s] Adding novel candidate %zx", __func__, flattenedHash);
+    mCandidates.emplace_front(flattenedHash, Prediction(layers, result));
     if (mCandidates.size() > MAX_CANDIDATES) {
         mCandidates.pop_back();
     }
@@ -476,4 +476,4 @@
     }
 }
 
-} // namespace android::compositionengine::impl::planner
\ No newline at end of file
+} // namespace android::compositionengine::impl::planner
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
index dcfc162..9dd199d 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
@@ -840,19 +840,19 @@
 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 @@
 
     expectNoSetCompositionTypeCall();
 
-    mOutputLayer.writeStateToHWC(true);
+    mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false);
 }
 
 TEST_F(OutputLayerTest, displayInstallOrientationBufferTransformSetTo90) {
@@ -891,7 +891,7 @@
     expectSetCompositionTypeCall(Hwc2::IComposerClient::Composition::SOLID_COLOR);
     expectSetColorCall();
 
-    mOutputLayer.writeStateToHWC(false);
+    mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, canSetPerFrameStateForSideband) {
@@ -901,7 +901,7 @@
     expectSetSidebandHandleCall();
     expectSetCompositionTypeCall(Hwc2::IComposerClient::Composition::SIDEBAND);
 
-    mOutputLayer.writeStateToHWC(false);
+    mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, canSetPerFrameStateForCursor) {
@@ -911,7 +911,7 @@
     expectSetHdrMetadataAndBufferCalls();
     expectSetCompositionTypeCall(Hwc2::IComposerClient::Composition::CURSOR);
 
-    mOutputLayer.writeStateToHWC(false);
+    mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, canSetPerFrameStateForDevice) {
@@ -921,7 +921,7 @@
     expectSetHdrMetadataAndBufferCalls();
     expectSetCompositionTypeCall(Hwc2::IComposerClient::Composition::DEVICE);
 
-    mOutputLayer.writeStateToHWC(false);
+    mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, compositionTypeIsNotSetIfUnchanged) {
@@ -934,7 +934,7 @@
     expectSetColorCall();
     expectNoSetCompositionTypeCall();
 
-    mOutputLayer.writeStateToHWC(false);
+    mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, compositionTypeIsSetToClientIfColorTransformNotSupported) {
@@ -944,7 +944,7 @@
     expectSetColorCall();
     expectSetCompositionTypeCall(Hwc2::IComposerClient::Composition::CLIENT);
 
-    mOutputLayer.writeStateToHWC(false);
+    mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, compositionTypeIsSetToClientIfClientCompositionForced) {
@@ -956,7 +956,7 @@
     expectSetColorCall();
     expectSetCompositionTypeCall(Hwc2::IComposerClient::Composition::CLIENT);
 
-    mOutputLayer.writeStateToHWC(false);
+    mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, allStateIncludesMetadataIfPresent) {
@@ -969,7 +969,7 @@
     expectGenericLayerMetadataCalls();
     expectSetCompositionTypeCall(Hwc2::IComposerClient::Composition::DEVICE);
 
-    mOutputLayer.writeStateToHWC(true);
+    mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, perFrameStateDoesNotIncludeMetadataIfPresent) {
@@ -980,7 +980,7 @@
     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 8dcace6..3059beb 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
@@ -783,11 +783,14 @@
     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 @@
     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 @@
     InjectedLayer layer3;
 
     EXPECT_CALL(*layer1.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0));
-    EXPECT_CALL(*layer1.outputLayer, writeStateToHWC(false));
+    EXPECT_CALL(*layer1.outputLayer,
+                writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false));
     EXPECT_CALL(*layer2.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0));
-    EXPECT_CALL(*layer2.outputLayer, writeStateToHWC(false));
+    EXPECT_CALL(*layer2.outputLayer,
+                writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false));
     EXPECT_CALL(*layer3.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0));
-    EXPECT_CALL(*layer3.outputLayer, writeStateToHWC(false));
+    EXPECT_CALL(*layer3.outputLayer,
+                writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false));
 
     injectOutputLayer(layer1);
     injectOutputLayer(layer2);
@@ -1648,6 +1657,7 @@
         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;
@@ -1667,6 +1677,7 @@
     EXPECT_CALL(mOutput, devOptRepaintFlash(Ref(args)));
     EXPECT_CALL(mOutput, finishFrame(Ref(args)));
     EXPECT_CALL(mOutput, postFramebuffer());
+    EXPECT_CALL(mOutput, renderCachedSets());
 
     mOutput.present(args);
 }
@@ -3482,7 +3493,8 @@
         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));
@@ -4063,11 +4075,14 @@
 
     // Layer requesting blur, or below, should request client composition.
     EXPECT_CALL(*layer1.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0));
-    EXPECT_CALL(*layer1.outputLayer, writeStateToHWC(false));
+    EXPECT_CALL(*layer1.outputLayer,
+                writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false));
     EXPECT_CALL(*layer2.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0));
-    EXPECT_CALL(*layer2.outputLayer, writeStateToHWC(false));
+    EXPECT_CALL(*layer2.outputLayer,
+                writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false));
     EXPECT_CALL(*layer3.outputLayer, updateCompositionState(false, false, ui::Transform::ROT_0));
-    EXPECT_CALL(*layer3.outputLayer, writeStateToHWC(false));
+    EXPECT_CALL(*layer3.outputLayer,
+                writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false));
 
     layer2.layerFEState.backgroundBlurRadius = 10;
 
@@ -4092,11 +4107,14 @@
 
     // 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);