Push HWC cache slot generation down into CompositionEngine

Stop caching buffers inside SurfaceFlinger and remove the tight coupling
between SurfaceFlinger's ClientCache and the Hardware Composer cache.
This allows a better seperation of responsibility, where buffer cache
management is not split between HwcSlotGenerator and HwcBufferCache, but
is instead solely handled by HwcBufferCache.

Note that FramebufferSurface and VirtualDisplaySurface no longer use
HwcBufferCache, but instead use their own cache slot management that
is solely based on BufferQueue slot numbers.

Also do minor refactoring in FramebufferSurface to simplify code.

Bug: 258196272
Test: started and stopped multiple YouTube videos on adt4 and verified
no change in graphic buffer usage
Test: atest HwcBufferCacheTest
Test: atest OutputPrepareTest

Change-Id: Ica7955ab4bc70e3c70207390e36dff73a2fc4949
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h
index f861fc9..415a041 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h
@@ -52,6 +52,9 @@
     // All the layers that have queued updates.
     Layers layersWithQueuedFrames;
 
+    // All graphic buffers that will no longer be used and should be removed from caches.
+    std::vector<uint64_t> bufferIdsToUncache;
+
     // Controls how the color mode is chosen for an output
     OutputColorSetting outputColorSetting{OutputColorSetting::kEnhanced};
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
index 974f7c6..ad98e93 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
@@ -163,7 +163,6 @@
 
     // The buffer and related state
     sp<GraphicBuffer> buffer;
-    int bufferSlot{BufferQueue::INVALID_BUFFER_SLOT};
     sp<Fence> acquireFence = Fence::NO_FENCE;
     Region surfaceDamage;
     uint64_t frameNumber = 0;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
index 874b330..bd43c89 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
@@ -23,6 +23,7 @@
 #include <type_traits>
 #include <unordered_map>
 #include <utility>
+#include <vector>
 
 #include <compositionengine/LayerFE.h>
 #include <renderengine/LayerSettings.h>
@@ -272,6 +273,7 @@
     virtual void setDisplayColorProfile(std::unique_ptr<DisplayColorProfile>) = 0;
     virtual void setRenderSurface(std::unique_ptr<RenderSurface>) = 0;
 
+    virtual void uncacheBuffers(const std::vector<uint64_t>&) = 0;
     virtual void rebuildLayerStacks(const CompositionRefreshArgs&, LayerFESet&) = 0;
     virtual void collectVisibleLayers(const CompositionRefreshArgs&, CoverageState&) = 0;
     virtual void ensureOutputLayerIfVisible(sp<LayerFE>&, CoverageState&) = 0;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h
index 6d0c395..4dbf8d2 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h
@@ -19,6 +19,7 @@
 #include <cstdint>
 #include <optional>
 #include <string>
+#include <vector>
 
 #include <ui/Transform.h>
 #include <utils/StrongPointer.h>
@@ -81,6 +82,10 @@
     // TODO(lpique): Make this protected once it is only internally called.
     virtual CompositionState& editState() = 0;
 
+    // Clear the cache entries for a set of buffers that SurfaceFlinger no
+    // longer cares about.
+    virtual void uncacheBuffers(const std::vector<uint64_t>& bufferIdsToUncache) = 0;
+
     // Recalculates the state of the output layer from the output-independent
     // layer. If includeGeometry is false, the geometry state can be skipped.
     // internalDisplayRotationFlags must be set to the rotation flags for the
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/HwcBufferCache.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/HwcBufferCache.h
index fd22aa3..6e9ea6f 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/HwcBufferCache.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/HwcBufferCache.h
@@ -17,7 +17,8 @@
 #pragma once
 
 #include <cstdint>
-#include <vector>
+#include <stack>
+#include <unordered_map>
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
 #pragma clang diagnostic push
@@ -37,35 +38,76 @@
 
 namespace compositionengine::impl {
 
-// With HIDLized hwcomposer HAL, the HAL can maintain a buffer cache for each
-// HWC display and layer.  When updating a display target or a layer buffer,
-// we have the option to send the buffer handle over or to request the HAL to
-// retrieve it from its cache.  The latter is cheaper since it eliminates the
-// overhead to transfer the handle over the trasport layer, and the overhead
-// for the HAL to clone and retain the handle.
-//
-// To be able to find out whether a buffer is already in the HAL's cache, we
-// use HWComposerBufferCache to mirror the cache in SF.
-class HwcBufferCache {
-public:
-    HwcBufferCache();
-    // Given a buffer, return the HWC cache slot and
-    // buffer to be sent to HWC.
-    //
-    // outBuffer is set to buffer when buffer is not in the HWC cache;
-    // otherwise, outBuffer is set to nullptr.
-    void getHwcBuffer(int slot, const sp<GraphicBuffer>& buffer, uint32_t* outSlot,
-                      sp<GraphicBuffer>* outBuffer);
+// The buffer cache returns both a slot and the buffer that should be sent to HWC. In cases
+// where the buffer is already cached, the buffer is a nullptr and will not be sent to HWC as
+// an optimization.
+struct HwcSlotAndBuffer {
+    uint32_t slot;
+    sp<GraphicBuffer> buffer;
+};
 
-    // Special caching slot for the layer caching feature.
-    static const constexpr size_t FLATTENER_CACHING_SLOT = BufferQueue::NUM_BUFFER_SLOTS;
+//
+// Manages the slot assignments for a buffers stored in Composer HAL's cache.
+//
+// Cache slots are an optimization when communicating buffer handles to Composer
+// HAL. When updating a layer's buffer, we can either send a new buffer handle
+// along with it's slot assignment or request the HAL to reuse a buffer handle
+// that we've already sent by using the slot assignment. The latter is cheaper
+// since it eliminates the overhead to transfer the buffer handle over IPC and
+// the overhead for the HAL to clone the handle.
+//
+class HwcBufferCache {
+private:
+    static const constexpr size_t kMaxLayerBufferCount = BufferQueue::NUM_BUFFER_SLOTS;
+
+public:
+    // public for testing
+    // Override buffers don't use the normal cache slots because we don't want them to evict client
+    // buffers from the cache. We add an extra slot at the end for the override buffers.
+    static const constexpr size_t kOverrideBufferSlot = kMaxLayerBufferCount;
+
+    HwcBufferCache();
+
+    //
+    // Given a buffer, return the HWC cache slot and buffer to send to HWC.
+    //
+    // If the buffer is already in the cache, the buffer is null to optimize away sending HWC the
+    // buffer handle.
+    //
+    HwcSlotAndBuffer getHwcSlotAndBuffer(const sp<GraphicBuffer>& buffer);
+    //
+    // Given a buffer, return the HWC cache slot and buffer to send to HWC.
+    //
+    // A special slot number is used for override buffers.
+    //
+    // If the buffer is already in the cache, the buffer is null to optimize away sending HWC the
+    // buffer handle.
+    //
+    HwcSlotAndBuffer getHwcSlotAndBufferForOverride(const sp<GraphicBuffer>& buffer);
+
+    //
+    // When a client process discards a buffer, it needs to be purged from the HWC cache.
+    //
+    // Returns the slot number of the buffer, or UINT32_MAX if it wasn't found in the cache.
+    //
+    uint32_t uncache(uint64_t graphicBufferId);
 
 private:
-    // an array where the index corresponds to a slot and the value corresponds to a (counter,
-    // buffer) pair. "counter" is a unique value that indicates the last time this slot was updated
-    // or used and allows us to keep track of the least-recently used buffer.
-    static const constexpr size_t kMaxLayerBufferCount = BufferQueue::NUM_BUFFER_SLOTS + 1;
-    wp<GraphicBuffer> mBuffers[kMaxLayerBufferCount];
+    uint32_t cache(const sp<GraphicBuffer>& buffer);
+    uint32_t getLeastRecentlyUsedSlot();
+
+    struct Cache {
+        sp<GraphicBuffer> buffer;
+        uint32_t slot;
+        // Cache entries are evicted according to least-recently-used when more than
+        // kMaxLayerBufferCount unique buffers have been sent to a layer.
+        uint64_t lruCounter;
+    };
+
+    std::unordered_map<uint64_t, Cache> mCacheByBufferId;
+    sp<GraphicBuffer> mLastOverrideBuffer;
+    std::stack<uint32_t> mFreeSlots;
+    uint64_t mLeastRecentlyUsedCounter;
 };
 
 } // namespace compositionengine::impl
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
index 9ca5da9..1393e29 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
@@ -82,6 +82,7 @@
     void prepare(const CompositionRefreshArgs&, LayerFESet&) override;
     void present(const CompositionRefreshArgs&) override;
 
+    void uncacheBuffers(const std::vector<uint64_t>& bufferIdsToUncache) override;
     void rebuildLayerStacks(const CompositionRefreshArgs&, LayerFESet&) override;
     void collectVisibleLayers(const CompositionRefreshArgs&,
                               compositionengine::Output::CoverageState&) override;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h
index 6d4abf9..f383392 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h
@@ -20,6 +20,7 @@
 #include <memory>
 #include <optional>
 #include <string>
+#include <vector>
 
 #include <compositionengine/LayerFE.h>
 #include <compositionengine/OutputLayer.h>
@@ -44,6 +45,8 @@
 
     void setHwcLayer(std::shared_ptr<HWC2::Layer>) override;
 
+    void uncacheBuffers(const std::vector<uint64_t>& bufferIdsToUncache) override;
+
     void updateCompositionState(bool includeGeometry, bool forceClientComposition,
                                 ui::Transform::RotationFlags) override;
     void writeStateToHWC(bool includeGeometry, bool skipLayer, uint32_t z, bool zIsOverridden,
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
index 7592cac..18e6879 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
@@ -82,6 +82,7 @@
     MOCK_METHOD2(prepare, void(const compositionengine::CompositionRefreshArgs&, LayerFESet&));
     MOCK_METHOD1(present, void(const compositionengine::CompositionRefreshArgs&));
 
+    MOCK_METHOD1(uncacheBuffers, void(const std::vector<uint64_t>&));
     MOCK_METHOD2(rebuildLayerStacks,
                  void(const compositionengine::CompositionRefreshArgs&, LayerFESet&));
     MOCK_METHOD2(collectVisibleLayers,
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/OutputLayer.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/OutputLayer.h
index c22f1bf..5fef63a 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/OutputLayer.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/OutputLayer.h
@@ -35,6 +35,8 @@
 
     MOCK_METHOD1(setHwcLayer, void(std::shared_ptr<HWC2::Layer>));
 
+    MOCK_METHOD1(uncacheBuffers, void(const std::vector<uint64_t>&));
+
     MOCK_CONST_METHOD0(getOutput, const compositionengine::Output&());
     MOCK_CONST_METHOD0(getLayerFE, compositionengine::LayerFE&());
 
diff --git a/services/surfaceflinger/CompositionEngine/src/HwcBufferCache.cpp b/services/surfaceflinger/CompositionEngine/src/HwcBufferCache.cpp
index f95382d..d64fd57 100644
--- a/services/surfaceflinger/CompositionEngine/src/HwcBufferCache.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/HwcBufferCache.cpp
@@ -16,43 +16,80 @@
 
 #include <compositionengine/impl/HwcBufferCache.h>
 
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-
 #include <gui/BufferQueue.h>
 #include <ui/GraphicBuffer.h>
 
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
-
 namespace android::compositionengine::impl {
 
 HwcBufferCache::HwcBufferCache() {
-    std::fill(std::begin(mBuffers), std::end(mBuffers), wp<GraphicBuffer>(nullptr));
+    for (uint32_t i = 0; i < kMaxLayerBufferCount; i++) {
+        mFreeSlots.push(i);
+    }
 }
 
-void HwcBufferCache::getHwcBuffer(int slot, const sp<GraphicBuffer>& buffer, uint32_t* outSlot,
-                                  sp<GraphicBuffer>* outBuffer) {
-    // default is 0
-    if (slot == BufferQueue::INVALID_BUFFER_SLOT || slot < 0 ||
-        slot >= static_cast<int32_t>(kMaxLayerBufferCount)) {
-        *outSlot = 0;
-    } else {
-        *outSlot = static_cast<uint32_t>(slot);
+HwcSlotAndBuffer HwcBufferCache::getHwcSlotAndBuffer(const sp<GraphicBuffer>& buffer) {
+    // TODO(b/261930578): This is for unit tests which don't mock GraphicBuffers but instead send
+    // in nullptrs.
+    if (buffer == nullptr) {
+        return {0, nullptr};
     }
-
-    auto& currentBuffer = mBuffers[*outSlot];
-    wp<GraphicBuffer> weakCopy(buffer);
-    if (currentBuffer == weakCopy) {
-        // already cached in HWC, skip sending the buffer
-        *outBuffer = nullptr;
-    } else {
-        *outBuffer = buffer;
-
-        // update cache
-        currentBuffer = buffer;
+    if (auto i = mCacheByBufferId.find(buffer->getId()); i != mCacheByBufferId.end()) {
+        Cache& cache = i->second;
+        // mark this cache slot as more recently used so it won't get evicted anytime soon
+        cache.lruCounter = mLeastRecentlyUsedCounter++;
+        return {cache.slot, nullptr};
     }
+    return {cache(buffer), buffer};
+}
+
+HwcSlotAndBuffer HwcBufferCache::getHwcSlotAndBufferForOverride(const sp<GraphicBuffer>& buffer) {
+    if (buffer == mLastOverrideBuffer) {
+        return {kOverrideBufferSlot, nullptr};
+    }
+    mLastOverrideBuffer = buffer;
+    return {kOverrideBufferSlot, buffer};
+}
+
+uint32_t HwcBufferCache::uncache(uint64_t bufferId) {
+    if (auto i = mCacheByBufferId.find(bufferId); i != mCacheByBufferId.end()) {
+        uint32_t slot = i->second.slot;
+        mCacheByBufferId.erase(i);
+        mFreeSlots.push(slot);
+        return slot;
+    }
+    if (mLastOverrideBuffer && bufferId == mLastOverrideBuffer->getId()) {
+        mLastOverrideBuffer = nullptr;
+        return kOverrideBufferSlot;
+    }
+    return UINT32_MAX;
+}
+
+uint32_t HwcBufferCache::cache(const sp<GraphicBuffer>& buffer) {
+    Cache cache;
+    cache.slot = getLeastRecentlyUsedSlot();
+    cache.lruCounter = mLeastRecentlyUsedCounter++;
+    cache.buffer = buffer;
+    mCacheByBufferId.emplace(buffer->getId(), cache);
+    return cache.slot;
+}
+
+uint32_t HwcBufferCache::getLeastRecentlyUsedSlot() {
+    if (mFreeSlots.empty()) {
+        assert(!mCacheByBufferId.empty());
+        // evict the least recently used cache entry
+        auto cacheToErase = mCacheByBufferId.begin();
+        for (auto i = cacheToErase; i != mCacheByBufferId.end(); ++i) {
+            if (i->second.lruCounter < cacheToErase->second.lruCounter) {
+                cacheToErase = i;
+            }
+        }
+        uint32_t slot = cacheToErase->second.slot;
+        mCacheByBufferId.erase(cacheToErase);
+        mFreeSlots.push(slot);
+    }
+    uint32_t slot = mFreeSlots.top();
+    mFreeSlots.pop();
+    return slot;
 }
 
 } // namespace android::compositionengine::impl
diff --git a/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp b/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp
index 6631a27..a405c4d 100644
--- a/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp
@@ -106,7 +106,6 @@
     dumpVal(out, "composition type", toString(compositionType), compositionType);
 
     out.append("\n      buffer: ");
-    dumpVal(out, "slot", bufferSlot);
     dumpVal(out, "buffer", buffer.get());
 
     out.append("\n      ");
diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp
index 3ee8017..16ef812 100644
--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -428,6 +428,7 @@
     ALOGV(__FUNCTION__);
 
     rebuildLayerStacks(refreshArgs, geomSnapshots);
+    uncacheBuffers(refreshArgs.bufferIdsToUncache);
 }
 
 void Output::present(const compositionengine::CompositionRefreshArgs& refreshArgs) {
@@ -455,6 +456,15 @@
     renderCachedSets(refreshArgs);
 }
 
+void Output::uncacheBuffers(std::vector<uint64_t> const& bufferIdsToUncache) {
+    if (bufferIdsToUncache.empty()) {
+        return;
+    }
+    for (auto outputLayer : getOutputLayersOrderedByZ()) {
+        outputLayer->uncacheBuffers(bufferIdsToUncache);
+    }
+}
+
 void Output::rebuildLayerStacks(const compositionengine::CompositionRefreshArgs& refreshArgs,
                                 LayerFESet& layerFESet) {
     ATRACE_CALL();
diff --git a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
index a39c527..a7c24b6 100644
--- a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
@@ -610,6 +610,19 @@
     }
 }
 
+void OutputLayer::uncacheBuffers(std::vector<uint64_t> const& bufferIdsToUncache) {
+    auto& state = editState();
+    // Skip doing this if there is no HWC interface
+    if (!state.hwc) {
+        return;
+    }
+
+    for (auto bufferId : bufferIdsToUncache) {
+        state.hwc->hwcBufferCache.uncache(bufferId);
+        // TODO(b/258196272): send uncache requests to Composer HAL
+    }
+}
+
 void OutputLayer::writeBufferStateToHWC(HWC2::Layer* hwcLayer,
                                         const LayerFECompositionState& outputIndependentState,
                                         bool skipLayer) {
@@ -622,27 +635,24 @@
               to_string(error).c_str(), static_cast<int32_t>(error));
     }
 
-    sp<GraphicBuffer> buffer = outputIndependentState.buffer;
-    sp<Fence> acquireFence = outputIndependentState.acquireFence;
-    int slot = outputIndependentState.bufferSlot;
+    HwcSlotAndBuffer hwcSlotAndBuffer;
+    sp<Fence> hwcFence;
+    // Override buffers use a special cache slot so that they don't evict client buffers.
     if (getState().overrideInfo.buffer != nullptr && !skipLayer) {
-        buffer = getState().overrideInfo.buffer->getBuffer();
-        acquireFence = getState().overrideInfo.acquireFence;
-        slot = HwcBufferCache::FLATTENER_CACHING_SLOT;
+        hwcSlotAndBuffer = editState().hwc->hwcBufferCache.getHwcSlotAndBufferForOverride(
+                getState().overrideInfo.buffer->getBuffer());
+        hwcFence = getState().overrideInfo.acquireFence;
+    } else {
+        hwcSlotAndBuffer =
+                editState().hwc->hwcBufferCache.getHwcSlotAndBuffer(outputIndependentState.buffer);
+        hwcFence = outputIndependentState.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(slot, buffer, &hwcSlot, &hwcBuffer);
-
-    if (auto error = hwcLayer->setBuffer(hwcSlot, hwcBuffer, acquireFence);
+    if (auto error = hwcLayer->setBuffer(hwcSlotAndBuffer.slot, hwcSlotAndBuffer.buffer, hwcFence);
         error != hal::Error::NONE) {
-        ALOGE("[%s] Failed to set buffer %p: %s (%d)", getLayerFE().getDebugName(), buffer->handle,
-              to_string(error).c_str(), static_cast<int32_t>(error));
+        ALOGE("[%s] Failed to set buffer %p: %s (%d)", getLayerFE().getDebugName(),
+              hwcSlotAndBuffer.buffer->handle, to_string(error).c_str(),
+              static_cast<int32_t>(error));
     }
 }
 
diff --git a/services/surfaceflinger/CompositionEngine/tests/HwcBufferCacheTest.cpp b/services/surfaceflinger/CompositionEngine/tests/HwcBufferCacheTest.cpp
index 7197780..cf72e8f 100644
--- a/services/surfaceflinger/CompositionEngine/tests/HwcBufferCacheTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/HwcBufferCacheTest.cpp
@@ -22,66 +22,172 @@
 namespace android::compositionengine {
 namespace {
 
-class TestableHwcBufferCache : public impl::HwcBufferCache {
-public:
-    void getHwcBuffer(int slot, const sp<GraphicBuffer>& buffer, uint32_t* outSlot,
-                      sp<GraphicBuffer>* outBuffer) {
-        HwcBufferCache::getHwcBuffer(slot, buffer, outSlot, outBuffer);
-    }
-};
+using impl::HwcBufferCache;
+using impl::HwcSlotAndBuffer;
 
 class HwcBufferCacheTest : public testing::Test {
 public:
     ~HwcBufferCacheTest() override = default;
 
-    void testSlot(const int inSlot, const uint32_t expectedSlot) {
-        uint32_t outSlot;
-        sp<GraphicBuffer> outBuffer;
-
-        // The first time, the output  is the same as the input
-        mCache.getHwcBuffer(inSlot, mBuffer1, &outSlot, &outBuffer);
-        EXPECT_EQ(expectedSlot, outSlot);
-        EXPECT_EQ(mBuffer1, outBuffer);
-
-        // The second time with the same buffer, the outBuffer is nullptr.
-        mCache.getHwcBuffer(inSlot, mBuffer1, &outSlot, &outBuffer);
-        EXPECT_EQ(expectedSlot, outSlot);
-        EXPECT_EQ(nullptr, outBuffer.get());
-
-        // With a new buffer, the outBuffer is the input.
-        mCache.getHwcBuffer(inSlot, mBuffer2, &outSlot, &outBuffer);
-        EXPECT_EQ(expectedSlot, outSlot);
-        EXPECT_EQ(mBuffer2, outBuffer);
-
-        // Again, the second request with the same buffer sets outBuffer to nullptr.
-        mCache.getHwcBuffer(inSlot, mBuffer2, &outSlot, &outBuffer);
-        EXPECT_EQ(expectedSlot, outSlot);
-        EXPECT_EQ(nullptr, outBuffer.get());
-
-        // Setting a slot to use nullptr lookslike works, but note that
-        // the output values make it look like no new buffer is being set....
-        mCache.getHwcBuffer(inSlot, sp<GraphicBuffer>(), &outSlot, &outBuffer);
-        EXPECT_EQ(expectedSlot, outSlot);
-        EXPECT_EQ(nullptr, outBuffer.get());
-    }
-
-    impl::HwcBufferCache mCache;
     sp<GraphicBuffer> mBuffer1 =
             sp<GraphicBuffer>::make(1u, 1u, HAL_PIXEL_FORMAT_RGBA_8888, 1u, 0u);
     sp<GraphicBuffer> mBuffer2 =
             sp<GraphicBuffer>::make(1u, 1u, HAL_PIXEL_FORMAT_RGBA_8888, 1u, 0u);
 };
 
-TEST_F(HwcBufferCacheTest, cacheWorksForSlotZero) {
-    testSlot(0, 0);
+TEST_F(HwcBufferCacheTest, getHwcSlotAndBuffer_returnsUniqueSlotNumberForEachBuffer) {
+    HwcBufferCache cache;
+    sp<GraphicBuffer> outBuffer;
+
+    HwcSlotAndBuffer slotAndBufferFor1 = cache.getHwcSlotAndBuffer(mBuffer1);
+    EXPECT_NE(slotAndBufferFor1.slot, UINT32_MAX);
+    EXPECT_EQ(slotAndBufferFor1.buffer, mBuffer1);
+
+    HwcSlotAndBuffer slotAndBufferFor2 = cache.getHwcSlotAndBuffer(mBuffer2);
+    EXPECT_NE(slotAndBufferFor2.slot, slotAndBufferFor1.slot);
+    EXPECT_NE(slotAndBufferFor2.slot, UINT32_MAX);
+    EXPECT_EQ(slotAndBufferFor2.buffer, mBuffer2);
 }
 
-TEST_F(HwcBufferCacheTest, cacheWorksForMaxSlot) {
-    testSlot(BufferQueue::NUM_BUFFER_SLOTS - 1, BufferQueue::NUM_BUFFER_SLOTS - 1);
+TEST_F(HwcBufferCacheTest, getHwcSlotAndBuffer_whenCached_returnsSameSlotNumberAndNullBuffer) {
+    HwcBufferCache cache;
+    sp<GraphicBuffer> outBuffer;
+
+    HwcSlotAndBuffer originalSlotAndBuffer = cache.getHwcSlotAndBuffer(mBuffer1);
+    EXPECT_NE(originalSlotAndBuffer.slot, UINT32_MAX);
+    EXPECT_EQ(originalSlotAndBuffer.buffer, mBuffer1);
+
+    HwcSlotAndBuffer finalSlotAndBuffer = cache.getHwcSlotAndBuffer(mBuffer1);
+    EXPECT_EQ(finalSlotAndBuffer.slot, originalSlotAndBuffer.slot);
+    EXPECT_EQ(finalSlotAndBuffer.buffer, nullptr);
 }
 
-TEST_F(HwcBufferCacheTest, cacheMapsNegativeSlotToZero) {
-    testSlot(-123, 0);
+TEST_F(HwcBufferCacheTest, getHwcSlotAndBuffer_whenSlotsFull_evictsOldestCachedBuffer) {
+    HwcBufferCache cache;
+    sp<GraphicBuffer> outBuffer;
+
+    sp<GraphicBuffer> graphicBuffers[100];
+    HwcSlotAndBuffer slotsAndBuffers[100];
+    int finalCachedBufferIndex = 0;
+    for (int i = 0; i < 100; ++i) {
+        graphicBuffers[i] = sp<GraphicBuffer>::make(1u, 1u, HAL_PIXEL_FORMAT_RGBA_8888, 1u, 0u);
+        slotsAndBuffers[i] = cache.getHwcSlotAndBuffer(graphicBuffers[i]);
+        // we fill up the cache when the slot number for the first buffer is reused
+        if (i > 0 && slotsAndBuffers[i].slot == slotsAndBuffers[0].slot) {
+            finalCachedBufferIndex = i;
+            break;
+        }
+    }
+    ASSERT_GT(finalCachedBufferIndex, 1);
+    // the final cached buffer has the same slot value as the oldest buffer
+    EXPECT_EQ(slotsAndBuffers[finalCachedBufferIndex].slot, slotsAndBuffers[0].slot);
+    // the oldest buffer is no longer in the cache because it was evicted
+    EXPECT_EQ(cache.uncache(graphicBuffers[0]->getId()), UINT32_MAX);
+}
+
+TEST_F(HwcBufferCacheTest, uncache_whenCached_returnsSlotNumber) {
+    HwcBufferCache cache;
+    sp<GraphicBuffer> outBuffer;
+
+    HwcSlotAndBuffer slotAndBufferFor1 = cache.getHwcSlotAndBuffer(mBuffer1);
+    ASSERT_NE(slotAndBufferFor1.slot, UINT32_MAX);
+
+    HwcSlotAndBuffer slotAndBufferFor2 = cache.getHwcSlotAndBuffer(mBuffer2);
+    ASSERT_NE(slotAndBufferFor2.slot, UINT32_MAX);
+
+    // the 1st buffer should be found in the cache with a slot number
+    EXPECT_EQ(cache.uncache(mBuffer1->getId()), slotAndBufferFor1.slot);
+    // since the 1st buffer has been previously uncached, we should no longer receive a slot number
+    EXPECT_EQ(cache.uncache(mBuffer1->getId()), UINT32_MAX);
+    // the 2nd buffer should be still found in the cache with a slot number
+    EXPECT_EQ(cache.uncache(mBuffer2->getId()), slotAndBufferFor2.slot);
+    // since the 2nd buffer has been previously uncached, we should no longer receive a slot number
+    EXPECT_EQ(cache.uncache(mBuffer2->getId()), UINT32_MAX);
+}
+
+TEST_F(HwcBufferCacheTest, uncache_whenUncached_returnsInvalidSlotNumber) {
+    HwcBufferCache cache;
+    sp<GraphicBuffer> outBuffer;
+
+    HwcSlotAndBuffer slotAndBufferFor1 = cache.getHwcSlotAndBuffer(mBuffer1);
+    ASSERT_NE(slotAndBufferFor1.slot, UINT32_MAX);
+
+    EXPECT_EQ(cache.uncache(mBuffer2->getId()), UINT32_MAX);
+}
+
+TEST_F(HwcBufferCacheTest, getHwcSlotAndBufferForOverride_whenCached_returnsSameSlotAndNullBuffer) {
+    HwcBufferCache cache;
+    sp<GraphicBuffer> outBuffer;
+
+    HwcSlotAndBuffer originalSlotAndBuffer = cache.getHwcSlotAndBufferForOverride(mBuffer1);
+    EXPECT_NE(originalSlotAndBuffer.slot, UINT32_MAX);
+    EXPECT_EQ(originalSlotAndBuffer.buffer, mBuffer1);
+
+    HwcSlotAndBuffer finalSlotAndBuffer = cache.getHwcSlotAndBufferForOverride(mBuffer1);
+    EXPECT_EQ(finalSlotAndBuffer.slot, originalSlotAndBuffer.slot);
+    EXPECT_EQ(finalSlotAndBuffer.buffer, nullptr);
+}
+
+TEST_F(HwcBufferCacheTest, getHwcSlotAndBufferForOverride_whenSlotsFull_returnsIndependentSlot) {
+    HwcBufferCache cache;
+    sp<GraphicBuffer> outBuffer;
+
+    sp<GraphicBuffer> graphicBuffers[100];
+    HwcSlotAndBuffer slotsAndBuffers[100];
+    int finalCachedBufferIndex = -1;
+    for (int i = 0; i < 100; ++i) {
+        graphicBuffers[i] = sp<GraphicBuffer>::make(1u, 1u, HAL_PIXEL_FORMAT_RGBA_8888, 1u, 0u);
+        slotsAndBuffers[i] = cache.getHwcSlotAndBuffer(graphicBuffers[i]);
+        // we fill up the cache when the slot number for the first buffer is reused
+        if (i > 0 && slotsAndBuffers[i].slot == slotsAndBuffers[0].slot) {
+            finalCachedBufferIndex = i;
+            break;
+        }
+    }
+    // expect to have cached at least a few buffers before evicting
+    ASSERT_GT(finalCachedBufferIndex, 1);
+
+    sp<GraphicBuffer> overrideBuffer =
+            sp<GraphicBuffer>::make(1u, 1u, HAL_PIXEL_FORMAT_RGBA_8888, 1u, 0u);
+    HwcSlotAndBuffer overrideSlotAndBuffer = cache.getHwcSlotAndBufferForOverride(overrideBuffer);
+    // expect us to have a slot number
+    EXPECT_NE(overrideSlotAndBuffer.slot, UINT32_MAX);
+    // expect this to be the first time we cached the buffer
+    EXPECT_NE(overrideSlotAndBuffer.buffer, nullptr);
+
+    // expect the slot number to not equal any other slot number, even after the slots have been
+    // exhausted, indicating that the override buffer slot is independent from the slots for
+    // non-override buffers
+    for (int i = 0; i < finalCachedBufferIndex; ++i) {
+        EXPECT_NE(overrideSlotAndBuffer.slot, slotsAndBuffers[i].slot);
+    }
+    // the override buffer is independently uncached from the oldest cached buffer
+    // expect to find the override buffer still in the override buffer slot
+    EXPECT_EQ(cache.uncache(overrideBuffer->getId()), overrideSlotAndBuffer.slot);
+    // expect that the first buffer was not evicted from the cache when the override buffer was
+    // cached
+    EXPECT_EQ(cache.uncache(graphicBuffers[1]->getId()), slotsAndBuffers[1].slot);
+}
+
+TEST_F(HwcBufferCacheTest, uncache_whenOverrideCached_returnsSlotNumber) {
+    HwcBufferCache cache;
+    sp<GraphicBuffer> outBuffer;
+
+    HwcSlotAndBuffer hwcSlotAndBuffer = cache.getHwcSlotAndBufferForOverride(mBuffer1);
+    ASSERT_NE(hwcSlotAndBuffer.slot, UINT32_MAX);
+
+    EXPECT_EQ(cache.uncache(mBuffer1->getId()), hwcSlotAndBuffer.slot);
+    EXPECT_EQ(cache.uncache(mBuffer1->getId()), UINT32_MAX);
+}
+
+TEST_F(HwcBufferCacheTest, uncache_whenOverrideUncached_returnsInvalidSlotNumber) {
+    HwcBufferCache cache;
+    sp<GraphicBuffer> outBuffer;
+
+    HwcSlotAndBuffer hwcSlotAndBuffer = cache.getHwcSlotAndBufferForOverride(mBuffer1);
+    ASSERT_NE(hwcSlotAndBuffer.slot, UINT32_MAX);
+
+    EXPECT_EQ(cache.uncache(mBuffer2->getId()), UINT32_MAX);
 }
 
 } // namespace
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
index 0f7dce8..03cf292 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
@@ -774,7 +774,7 @@
     static constexpr ui::Dataspace kOverrideDataspace = static_cast<ui::Dataspace>(72);
     static constexpr int kSupportedPerFrameMetadata = 101;
     static constexpr int kExpectedHwcSlot = 0;
-    static constexpr int kOverrideHwcSlot = impl::HwcBufferCache::FLATTENER_CACHING_SLOT;
+    static constexpr int kOverrideHwcSlot = impl::HwcBufferCache::kOverrideBufferSlot;
     static constexpr bool kLayerGenericMetadata1Mandatory = true;
     static constexpr bool kLayerGenericMetadata2Mandatory = true;
     static constexpr float kWhitePointNits = 200.f;
@@ -823,7 +823,6 @@
         mLayerFEState.hdrMetadata = kHdrMetadata;
         mLayerFEState.sidebandStream = NativeHandle::create(kSidebandStreamHandle, false);
         mLayerFEState.buffer = kBuffer;
-        mLayerFEState.bufferSlot = BufferQueue::INVALID_BUFFER_SLOT;
         mLayerFEState.acquireFence = kFence;
 
         mOutputState.displayBrightnessNits = kDisplayBrightnessNits;
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
index 2109987..bfd863b 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
@@ -1192,14 +1192,49 @@
                           compositionengine::LayerFESet&));
     };
 
+    OutputPrepareTest() {
+        EXPECT_CALL(mOutput, getOutputLayerCount()).WillRepeatedly(Return(2u));
+        EXPECT_CALL(mOutput, getOutputLayerOrderedByZByIndex(0))
+                .WillRepeatedly(Return(&mLayer1.outputLayer));
+        EXPECT_CALL(mOutput, getOutputLayerOrderedByZByIndex(1))
+                .WillRepeatedly(Return(&mLayer2.outputLayer));
+
+        mRefreshArgs.layers.push_back(mLayer1.layerFE);
+        mRefreshArgs.layers.push_back(mLayer2.layerFE);
+    }
+
+    struct Layer {
+        StrictMock<mock::OutputLayer> outputLayer;
+        sp<StrictMock<mock::LayerFE>> layerFE = sp<StrictMock<mock::LayerFE>>::make();
+    };
+
     StrictMock<OutputPartialMock> mOutput;
     CompositionRefreshArgs mRefreshArgs;
     LayerFESet mGeomSnapshots;
+    Layer mLayer1;
+    Layer mLayer2;
 };
 
-TEST_F(OutputPrepareTest, justInvokesRebuildLayerStacks) {
+TEST_F(OutputPrepareTest, callsUncacheBuffersOnEachOutputLayerAndThenRebuildsLayerStacks) {
     InSequence seq;
+
+    mRefreshArgs.bufferIdsToUncache = {1, 3, 5};
+
     EXPECT_CALL(mOutput, rebuildLayerStacks(Ref(mRefreshArgs), Ref(mGeomSnapshots)));
+    EXPECT_CALL(mLayer1.outputLayer, uncacheBuffers(Ref(mRefreshArgs.bufferIdsToUncache)));
+    EXPECT_CALL(mLayer2.outputLayer, uncacheBuffers(Ref(mRefreshArgs.bufferIdsToUncache)));
+
+    mOutput.prepare(mRefreshArgs, mGeomSnapshots);
+}
+
+TEST_F(OutputPrepareTest, skipsUncacheBuffersIfEmptyAndThenRebuildsLayerStacks) {
+    InSequence seq;
+
+    mRefreshArgs.bufferIdsToUncache = {};
+
+    EXPECT_CALL(mOutput, rebuildLayerStacks(Ref(mRefreshArgs), Ref(mGeomSnapshots)));
+    EXPECT_CALL(mLayer1.outputLayer, uncacheBuffers(_)).Times(0);
+    EXPECT_CALL(mLayer2.outputLayer, uncacheBuffers(_)).Times(0);
 
     mOutput.prepare(mRefreshArgs, mGeomSnapshots);
 }