Add CachingHint into SurfaceFlinger

Some system layers may need to be excluded from surfaceflinger caching
for a variety of reasons, including power or image quality
considerations. This provides a mechanism for those system layers to
never be cached on the GPU.

Bug: 259311918
Test: libcompositionengine_test
Test: run test app and inspect planner state
Change-Id: I35418ad5a41cb2546e3b27b0af290bf3c31a0180
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
index 32684fc..0b4d20c 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
@@ -18,6 +18,7 @@
 
 #include <cstdint>
 
+#include <android/gui/CachingHint.h>
 #include <gui/HdrMetadata.h>
 #include <math/mat4.h>
 #include <ui/BlurRegion.h>
@@ -213,6 +214,7 @@
     float desiredSdrHdrRatio = 1.f;
 
     bool isInternalDisplayOverlay = false;
+    gui::CachingHint cachingHint = gui::CachingHint::Enabled;
 
     virtual ~LayerFECompositionState();
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h
index 24a7744..d26ca9d 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h
@@ -149,6 +149,9 @@
 
     bool hasSolidColorLayers() const;
 
+    // True if any layer in this cached set has CachingHint::Disabled
+    bool cachingHintExcludesLayers() const;
+
 private:
     const NonBufferHash mFingerprint;
     std::chrono::steady_clock::time_point mLastUpdate = std::chrono::steady_clock::now();
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h
index e309442..d5c488e 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h
@@ -73,6 +73,7 @@
     BackgroundBlurRadius  = 1u << 17,
     BlurRegions           = 1u << 18,
     HasProtectedContent   = 1u << 19,
+    CachingHint           = 1u << 20,
 };
 // clang-format on
 
@@ -250,6 +251,8 @@
 
     bool isProtected() const { return mIsProtected.get(); }
 
+    gui::CachingHint getCachingHint() const { return mCachingHint.get(); }
+
     bool hasSolidColorCompositionType() const {
         return getOutputLayer()->getLayerFE().getCompositionState()->compositionType ==
                 aidl::android::hardware::graphics::composer3::Composition::SOLID_COLOR;
@@ -487,7 +490,15 @@
         return layer->getLayerFE().getCompositionState()->hasProtectedContent;
     }};
 
-    static const constexpr size_t kNumNonUniqueFields = 18;
+    OutputLayerState<gui::CachingHint, LayerStateField::CachingHint>
+            mCachingHint{[](auto layer) {
+                             return layer->getLayerFE().getCompositionState()->cachingHint;
+                         },
+                         [](const gui::CachingHint& cachingHint) {
+                             return std::vector<std::string>{toString(cachingHint)};
+                         }};
+
+    static const constexpr size_t kNumNonUniqueFields = 19;
 
     std::array<StateInterface*, kNumNonUniqueFields> getNonUniqueFields() {
         std::array<const StateInterface*, kNumNonUniqueFields> constFields =
@@ -501,13 +512,11 @@
     }
 
     std::array<const StateInterface*, kNumNonUniqueFields> getNonUniqueFields() const {
-        return {
-                &mDisplayFrame, &mSourceCrop,     &mBufferTransform,      &mBlendMode,
+        return {&mDisplayFrame, &mSourceCrop,     &mBufferTransform,      &mBlendMode,
                 &mAlpha,        &mLayerMetadata,  &mVisibleRegion,        &mOutputDataspace,
                 &mPixelFormat,  &mColorTransform, &mCompositionType,      &mSidebandStream,
                 &mBuffer,       &mSolidColor,     &mBackgroundBlurRadius, &mBlurRegions,
-                &mFrameNumber,  &mIsProtected,
-        };
+                &mFrameNumber,  &mIsProtected,    &mCachingHint};
     }
 };
 
diff --git a/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp b/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp
index 531b659..615d04b 100644
--- a/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp
@@ -126,6 +126,7 @@
         dumpVal(out, "desired sdr/hdr ratio", desiredSdrHdrRatio);
     }
     dumpVal(out, "colorTransform", colorTransform);
+    dumpVal(out, "caching hint", toString(cachingHint));
 
     out.append("\n");
 }
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
index ed9a88d..a00ce57 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
@@ -393,6 +393,18 @@
     });
 }
 
+bool CachedSet::cachingHintExcludesLayers() const {
+    const bool shouldExcludeLayers =
+            std::any_of(mLayers.cbegin(), mLayers.cend(), [](const Layer& layer) {
+                return layer.getState()->getCachingHint() == gui::CachingHint::Disabled;
+            });
+
+    LOG_ALWAYS_FATAL_IF(shouldExcludeLayers && getLayerCount() > 1,
+                        "CachedSet is invalid: should be excluded but contains %zu layers",
+                        getLayerCount());
+    return shouldExcludeLayers;
+}
+
 void CachedSet::dump(std::string& result) const {
     const auto now = std::chrono::steady_clock::now();
 
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
index 9175dd0..13b6307 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
@@ -413,6 +413,7 @@
     for (auto currentSet = mLayers.cbegin(); currentSet != mLayers.cend(); ++currentSet) {
         bool layerIsInactive = now - currentSet->getLastUpdate() > mTunables.mActiveLayerTimeout;
         const bool layerHasBlur = currentSet->hasBlurBehind();
+        const bool layerDeniedFromCaching = currentSet->cachingHintExcludesLayers();
 
         // Layers should also be considered inactive whenever their framerate is lower than 1fps.
         if (!layerIsInactive && currentSet->getLayerCount() == kNumLayersFpsConsideration) {
@@ -424,7 +425,8 @@
             }
         }
 
-        if (layerIsInactive && (firstLayer || runHasFirstLayer || !layerHasBlur) &&
+        if (!layerDeniedFromCaching && layerIsInactive &&
+            (firstLayer || runHasFirstLayer || !layerHasBlur) &&
             !currentSet->hasUnsupportedDataspace()) {
             if (isPartOfRun) {
                 builder.increment();
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp
index d5d688e..ca5ba69 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp
@@ -577,6 +577,20 @@
     cachedSet.append(CachedSet(layer3));
 }
 
+TEST_F(CachedSetTest, cachingHintIncludesLayersByDefault) {
+    CachedSet cachedSet(*mTestLayers[0]->cachedSetLayer.get());
+    EXPECT_FALSE(cachedSet.cachingHintExcludesLayers());
+}
+
+TEST_F(CachedSetTest, cachingHintExcludesLayersWhenDisabled) {
+    CachedSet::Layer& layer1 = *mTestLayers[0]->cachedSetLayer.get();
+    mTestLayers[0]->layerFECompositionState.cachingHint = gui::CachingHint::Disabled;
+    mTestLayers[0]->layerState->update(&mTestLayers[0]->outputLayer);
+
+    CachedSet cachedSet(layer1);
+    EXPECT_TRUE(cachedSet.cachingHintExcludesLayers());
+}
+
 TEST_F(CachedSetTest, holePunch_requiresBuffer) {
     CachedSet::Layer& layer1 = *mTestLayers[0]->cachedSetLayer.get();
     auto& layerFECompositionState = mTestLayers[0]->layerFECompositionState;
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
index 86cfee6..778a0a8 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
@@ -1112,6 +1112,55 @@
                                  true);
 }
 
+TEST_F(FlattenerTest, flattenLayers_skipsLayersDisabledFromCaching) {
+    auto& layerState1 = mTestLayers[0]->layerState;
+    const auto& overrideBuffer1 = layerState1->getOutputLayer()->getState().overrideInfo.buffer;
+
+    auto& layerState2 = mTestLayers[1]->layerState;
+    const auto& overrideBuffer2 = layerState2->getOutputLayer()->getState().overrideInfo.buffer;
+
+    // The third layer has a CachingHint that prevents caching from running
+    auto& layerState3 = mTestLayers[2]->layerState;
+    const auto& overrideBuffer3 = layerState3->getOutputLayer()->getState().overrideInfo.buffer;
+    mTestLayers[2]->layerFECompositionState.cachingHint = gui::CachingHint::Disabled;
+    mTestLayers[2]->layerState->update(&mTestLayers[2]->outputLayer);
+
+    const std::vector<const LayerState*> layers = {
+            layerState1.get(),
+            layerState2.get(),
+            layerState3.get(),
+    };
+
+    initializeFlattener(layers);
+
+    mTime += 200ms;
+    initializeOverrideBuffer(layers);
+    EXPECT_EQ(getNonBufferHash(layers),
+              mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
+
+    // This will render a CachedSet.
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+            .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
+    mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
+
+    // We've rendered a CachedSet, but we haven't merged it in.
+    EXPECT_EQ(nullptr, overrideBuffer1);
+    EXPECT_EQ(nullptr, overrideBuffer2);
+    EXPECT_EQ(nullptr, overrideBuffer3);
+
+    // This time we merge the CachedSet in, so we have a new hash, and we should
+    // only have two sets.
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).Times(0);
+    initializeOverrideBuffer(layers);
+    EXPECT_NE(getNonBufferHash(layers),
+              mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
+    mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
+
+    EXPECT_NE(nullptr, overrideBuffer1);
+    EXPECT_EQ(overrideBuffer1, overrideBuffer2);
+    EXPECT_EQ(nullptr, overrideBuffer3);
+}
+
 TEST_F(FlattenerTest, flattenLayers_skipsBT601_625) {
     auto& layerState1 = mTestLayers[0]->layerState;
     const auto& overrideBuffer1 = layerState1->getOutputLayer()->getState().overrideInfo.buffer;
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp
index 47b6820..044917e 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp
@@ -994,6 +994,45 @@
     EXPECT_TRUE(mLayerState->hasBlurBehind());
 }
 
+TEST_F(LayerStateTest, updateCachingHint) {
+    OutputLayerCompositionState outputLayerCompositionState;
+    LayerFECompositionState layerFECompositionState;
+    layerFECompositionState.cachingHint = gui::CachingHint::Enabled;
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
+                       layerFECompositionState);
+    mLayerState = std::make_unique<LayerState>(&mOutputLayer);
+
+    mock::OutputLayer newOutputLayer;
+    sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
+    LayerFECompositionState layerFECompositionStateTwo;
+    layerFECompositionStateTwo.cachingHint = gui::CachingHint::Disabled;
+    setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
+                       layerFECompositionStateTwo);
+    ftl::Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
+    EXPECT_EQ(ftl::Flags<LayerStateField>(LayerStateField::CachingHint), updates);
+}
+
+TEST_F(LayerStateTest, compareCachingHint) {
+    OutputLayerCompositionState outputLayerCompositionState;
+    LayerFECompositionState layerFECompositionState;
+    layerFECompositionState.cachingHint = gui::CachingHint::Enabled;
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
+                       layerFECompositionState);
+    mLayerState = std::make_unique<LayerState>(&mOutputLayer);
+    mock::OutputLayer newOutputLayer;
+    sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
+    LayerFECompositionState layerFECompositionStateTwo;
+    layerFECompositionStateTwo.cachingHint = gui::CachingHint::Disabled;
+    setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
+                       layerFECompositionStateTwo);
+    auto otherLayerState = std::make_unique<LayerState>(&newOutputLayer);
+
+    verifyNonUniqueDifferingFields(*mLayerState, *otherLayerState, LayerStateField::CachingHint);
+
+    EXPECT_TRUE(mLayerState->compare(*otherLayerState));
+    EXPECT_TRUE(otherLayerState->compare(*mLayerState));
+}
+
 TEST_F(LayerStateTest, dumpDoesNotCrash) {
     OutputLayerCompositionState outputLayerCompositionState;
     LayerFECompositionState layerFECompositionState;
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
index c9aeb24..d740350 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
@@ -727,6 +727,7 @@
         snapshot.dimmingEnabled = requested.dimmingEnabled;
         snapshot.layerOpaqueFlagSet =
                 (requested.flags & layer_state_t::eLayerOpaque) == layer_state_t::eLayerOpaque;
+        snapshot.cachingHint = requested.cachingHint;
     }
 
     if (forceUpdate || snapshot.changes.any(RequestedLayerState::Changes::Content)) {
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
index 2834084..09523d3 100644
--- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
@@ -137,6 +137,7 @@
     dataspace = ui::Dataspace::V0_SRGB;
     gameMode = gui::GameMode::Unsupported;
     requestedFrameRate = {};
+    cachingHint = gui::CachingHint::Enabled;
 }
 
 void RequestedLayerState::merge(const ResolvedComposerState& resolvedComposerState) {
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 084d9b9..dac0916 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -566,6 +566,7 @@
                                         : Hwc2::IComposerClient::BlendMode::COVERAGE;
     }
 
+    // Please keep in sync with LayerSnapshotBuilder
     auto* snapshot = editLayerSnapshot();
     snapshot->outputFilter = getOutputFilter();
     snapshot->isVisible = isVisible();
@@ -592,6 +593,7 @@
     const auto& drawingState{getDrawingState()};
     auto* snapshot = editLayerSnapshot();
 
+    // Please keep in sync with LayerSnapshotBuilder
     snapshot->geomBufferSize = getBufferSize(drawingState);
     snapshot->geomContentCrop = getBufferCrop();
     snapshot->geomCrop = getCrop(drawingState);
@@ -624,6 +626,7 @@
 
 void Layer::preparePerFrameCompositionState() {
     const auto& drawingState{getDrawingState()};
+    // Please keep in sync with LayerSnapshotBuilder
     auto* snapshot = editLayerSnapshot();
 
     snapshot->forceClientComposition = false;
@@ -637,6 +640,7 @@
     snapshot->dimmingEnabled = isDimmingEnabled();
     snapshot->currentSdrHdrRatio = getCurrentSdrHdrRatio();
     snapshot->desiredSdrHdrRatio = getDesiredSdrHdrRatio();
+    snapshot->cachingHint = getCachingHint();
 
     const bool usesRoundedCorners = hasRoundedCorners();
 
@@ -666,8 +670,9 @@
 }
 
 void Layer::preparePerFrameBufferCompositionState() {
-    // Sideband layers
+    // Please keep in sync with LayerSnapshotBuilder
     auto* snapshot = editLayerSnapshot();
+    // Sideband layers
     if (snapshot->sidebandStream.get() && !snapshot->sidebandStreamHasFrame) {
         snapshot->compositionType =
                 aidl::android::hardware::graphics::composer3::Composition::SIDEBAND;
@@ -690,6 +695,7 @@
 }
 
 void Layer::preparePerFrameEffectsCompositionState() {
+    // Please keep in sync with LayerSnapshotBuilder
     auto* snapshot = editLayerSnapshot();
     snapshot->color = getColor();
     snapshot->compositionType =
@@ -698,6 +704,7 @@
 
 void Layer::prepareCursorCompositionState() {
     const State& drawingState{getDrawingState()};
+    // Please keep in sync with LayerSnapshotBuilder
     auto* snapshot = editLayerSnapshot();
 
     // Apply the layer's transform, followed by the display's global transform
@@ -3091,6 +3098,14 @@
     return true;
 }
 
+bool Layer::setCachingHint(gui::CachingHint cachingHint) {
+    if (mDrawingState.cachingHint == cachingHint) return false;
+    mDrawingState.cachingHint = cachingHint;
+    mDrawingState.modified = true;
+    setTransactionFlags(eTransactionNeeded);
+    return true;
+}
+
 bool Layer::setHdrMetadata(const HdrMetadata& hdrMetadata) {
     if (mDrawingState.hdrMetadata == hdrMetadata) return false;
     mDrawingState.hdrMetadata = hdrMetadata;
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 2955daf..7d40774 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -227,6 +227,7 @@
         bool dimmingEnabled = true;
         float currentSdrHdrRatio = 1.f;
         float desiredSdrHdrRatio = 1.f;
+        gui::CachingHint cachingHint = gui::CachingHint::Enabled;
     };
 
     explicit Layer(const LayerCreationArgs& args);
@@ -296,6 +297,7 @@
     virtual bool isDimmingEnabled() const { return getDrawingState().dimmingEnabled; }
     float getDesiredSdrHdrRatio() const { return getDrawingState().desiredSdrHdrRatio; }
     float getCurrentSdrHdrRatio() const { return getDrawingState().currentSdrHdrRatio; }
+    gui::CachingHint getCachingHint() const { return getDrawingState().cachingHint; }
 
     bool setTransform(uint32_t /*transform*/);
     bool setTransformToDisplayInverse(bool /*transformToDisplayInverse*/);
@@ -305,6 +307,7 @@
                    std::optional<nsecs_t> /* dequeueTime */, const FrameTimelineInfo& /*info*/);
     bool setDataspace(ui::Dataspace /*dataspace*/);
     bool setExtendedRangeBrightness(float currentBufferRatio, float desiredRatio);
+    bool setCachingHint(gui::CachingHint cachingHint);
     bool setHdrMetadata(const HdrMetadata& /*hdrMetadata*/);
     bool setSurfaceDamageRegion(const Region& /*surfaceDamage*/);
     bool setApi(int32_t /*api*/);
diff --git a/services/surfaceflinger/LayerFE.h b/services/surfaceflinger/LayerFE.h
index 01da019..c23bd31 100644
--- a/services/surfaceflinger/LayerFE.h
+++ b/services/surfaceflinger/LayerFE.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <android/gui/CachingHint.h>
 #include <gui/LayerMetadata.h>
 #include "FrontEnd/LayerSnapshot.h"
 #include "compositionengine/LayerFE.h"
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 5f95859..5c8579c 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -4783,6 +4783,11 @@
             flags |= eTraversalNeeded;
         }
     }
+    if (what & layer_state_t::eCachingHintChanged) {
+        if (layer->setCachingHint(s.cachingHint)) {
+            flags |= eTraversalNeeded;
+        }
+    }
     if (what & layer_state_t::eHdrMetadataChanged) {
         if (layer->setHdrMetadata(s.hdrMetadata)) flags |= eTraversalNeeded;
     }