blast: cache buffers

Cache incoming buffers so they don't have to be imported by mapper
everytime they are set.

Amended by Change-Id: I3040f6d8886ca9b130115784c199edfdd9c85c7e

Test: Transaction_test
Bug: 80477568
Change-Id: Icd167c5e5bd59d7331b829667b1139919393d98b
diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp
index c3a4737..ad2dc14 100644
--- a/libs/gui/ISurfaceComposer.cpp
+++ b/libs/gui/ISurfaceComposer.cpp
@@ -695,6 +695,42 @@
         }
         return err;
     }
+
+    virtual status_t cacheBuffer(const sp<IBinder>& token, const sp<GraphicBuffer>& buffer,
+                                 int32_t* outBufferId) {
+        Parcel data, reply;
+        data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
+
+        data.writeStrongBinder(token);
+        if (buffer) {
+            data.writeBool(true);
+            data.write(*buffer);
+        } else {
+            data.writeBool(false);
+        }
+
+        status_t result = remote()->transact(BnSurfaceComposer::CACHE_BUFFER, data, &reply);
+        if (result != NO_ERROR) {
+            return result;
+        }
+
+        int32_t id = -1;
+        result = reply.readInt32(&id);
+        if (result == NO_ERROR) {
+            *outBufferId = id;
+        }
+        return result;
+    }
+
+    virtual status_t uncacheBuffer(const sp<IBinder>& token, int32_t bufferId) {
+        Parcel data, reply;
+        data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
+
+        data.writeStrongBinder(token);
+        data.writeInt32(bufferId);
+
+        return remote()->transact(BnSurfaceComposer::UNCACHE_BUFFER, data, &reply);
+    }
 };
 
 // Out-of-line virtual method definition to trigger vtable emission in this
@@ -1136,6 +1172,48 @@
             }
             return error;
         }
+        case CACHE_BUFFER: {
+            CHECK_INTERFACE(ISurfaceComposer, data, reply);
+            sp<IBinder> token;
+            status_t result = data.readStrongBinder(&token);
+            if (result != NO_ERROR) {
+                ALOGE("cache buffer failure in reading token: %d", result);
+                return result;
+            }
+
+            sp<GraphicBuffer> buffer = new GraphicBuffer();
+            if (data.readBool()) {
+                result = data.read(*buffer);
+                if (result != NO_ERROR) {
+                    ALOGE("cache buffer failure in reading buffer: %d", result);
+                    return result;
+                }
+            }
+            int32_t bufferId = -1;
+            status_t error = cacheBuffer(token, buffer, &bufferId);
+            if (error == NO_ERROR) {
+                reply->writeInt32(bufferId);
+            }
+            return error;
+        }
+        case UNCACHE_BUFFER: {
+            CHECK_INTERFACE(ISurfaceComposer, data, reply);
+            sp<IBinder> token;
+            status_t result = data.readStrongBinder(&token);
+            if (result != NO_ERROR) {
+                ALOGE("uncache buffer failure in reading token: %d", result);
+                return result;
+            }
+
+            int32_t bufferId = -1;
+            result = data.readInt32(&bufferId);
+            if (result != NO_ERROR) {
+                ALOGE("uncache buffer failure in reading buffer id: %d", result);
+                return result;
+            }
+
+            return uncacheBuffer(token, bufferId);
+        }
         default: {
             return BBinder::onTransact(code, data, reply, flags);
         }
diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp
index e5170ab..40b55fa 100644
--- a/libs/gui/LayerState.cpp
+++ b/libs/gui/LayerState.cpp
@@ -94,6 +94,9 @@
         }
     }
 
+    output.writeStrongBinder(cachedBuffer.token);
+    output.writeInt32(cachedBuffer.bufferId);
+
     return NO_ERROR;
 }
 
@@ -164,6 +167,9 @@
         listenerCallbacks.emplace_back(listener, callbackIds);
     }
 
+    cachedBuffer.token = input.readStrongBinder();
+    cachedBuffer.bufferId = input.readInt32();
+
     return NO_ERROR;
 }
 
@@ -372,6 +378,10 @@
     }
 #endif
 
+    if (other.what & eCachedBufferChanged) {
+        what |= eCachedBufferChanged;
+        cachedBuffer = other.cachedBuffer;
+    }
     if ((other.what & what) != other.what) {
         ALOGE("Unmerged SurfaceComposer Transaction properties. LayerState::merge needs updating? "
               "other.what=0x%" PRIu64 " what=0x%" PRIu64,
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index 32a368e..35659e9 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -660,6 +660,21 @@
     return *this;
 }
 
+SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setCachedBuffer(
+        const sp<SurfaceControl>& sc, int32_t bufferId) {
+    layer_state_t* s = getLayerState(sc);
+    if (!s) {
+        mStatus = BAD_INDEX;
+        return *this;
+    }
+    s->what |= layer_state_t::eCachedBufferChanged;
+    s->cachedBuffer.token = IInterface::asBinder(TransactionCompletedListener::getIInstance());
+    s->cachedBuffer.bufferId = bufferId;
+
+    registerSurfaceControlForCallback(sc);
+    return *this;
+}
+
 SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setAcquireFence(
         const sp<SurfaceControl>& sc, const sp<Fence>& fence) {
     layer_state_t* s = getLayerState(sc);
@@ -1057,6 +1072,26 @@
 
 // ----------------------------------------------------------------------------
 
+status_t SurfaceComposerClient::cacheBuffer(const sp<GraphicBuffer>& buffer, int32_t* outBufferId) {
+    sp<ISurfaceComposer> sf(ComposerService::getComposerService());
+    if (buffer == nullptr || outBufferId == nullptr) {
+        return BAD_VALUE;
+    }
+    return sf->cacheBuffer(IInterface::asBinder(TransactionCompletedListener::getIInstance()),
+                           buffer, outBufferId);
+}
+
+status_t SurfaceComposerClient::uncacheBuffer(int32_t bufferId) {
+    sp<ISurfaceComposer> sf(ComposerService::getComposerService());
+    if (bufferId < 0) {
+        return BAD_VALUE;
+    }
+    return sf->uncacheBuffer(IInterface::asBinder(TransactionCompletedListener::getIInstance()),
+                             bufferId);
+}
+
+// ----------------------------------------------------------------------------
+
 status_t SurfaceComposerClient::enableVSyncInjections(bool enable) {
     sp<ISurfaceComposer> sf(ComposerService::getComposerService());
     return sf->enableVSyncInjections(enable);
diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h
index 25a4185..418d5fb 100644
--- a/libs/gui/include/gui/ISurfaceComposer.h
+++ b/libs/gui/include/gui/ISurfaceComposer.h
@@ -316,6 +316,11 @@
      * Requires the ACCESS_SURFACE_FLINGER permission.
      */
     virtual status_t getProtectedContentSupport(bool* outSupported) const = 0;
+
+    virtual status_t cacheBuffer(const sp<IBinder>& token, const sp<GraphicBuffer>& buffer,
+                                 int32_t* outBufferId) = 0;
+
+    virtual status_t uncacheBuffer(const sp<IBinder>& token, int32_t bufferId) = 0;
 };
 
 // ----------------------------------------------------------------------------
@@ -358,6 +363,8 @@
         SET_DISPLAY_CONTENT_SAMPLING_ENABLED,
         GET_DISPLAYED_CONTENT_SAMPLE,
         GET_PROTECTED_CONTENT_SUPPORT,
+        CACHE_BUFFER,
+        UNCACHE_BUFFER,
     };
 
     virtual status_t onTransact(uint32_t code, const Parcel& data,
diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h
index e7564f5..af31420 100644
--- a/libs/gui/include/gui/LayerState.h
+++ b/libs/gui/include/gui/LayerState.h
@@ -84,6 +84,7 @@
         eInputInfoChanged = 0x40000000,
         eCornerRadiusChanged = 0x80000000,
         eFrameChanged = 0x1'00000000,
+        eCachedBufferChanged = 0x2'00000000,
     };
 
     layer_state_t()
@@ -125,6 +126,10 @@
         float dtdy{0};
         float dsdy{0};
     };
+    struct cached_buffer_t {
+        sp<IBinder> token = nullptr;
+        int32_t bufferId = -1;
+    };
     sp<IBinder> surface;
     uint64_t what;
     float x;
@@ -173,6 +178,8 @@
 #ifndef NO_INPUT
     InputWindowInfo inputInfo;
 #endif
+
+    cached_buffer_t cachedBuffer;
 };
 
 struct ComposerState {
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index 2f02328..78515d0 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -152,6 +152,12 @@
     static void doDropReferenceTransaction(const sp<IBinder>& handle,
             const sp<ISurfaceComposerClient>& client);
 
+    // Caches a buffer with the ISurfaceComposer so the buffer does not need to be resent across
+    // processes
+    static status_t cacheBuffer(const sp<GraphicBuffer>& buffer, int32_t* outBufferId);
+    // Uncaches a buffer set by cacheBuffer
+    static status_t uncacheBuffer(int32_t bufferId);
+
     // ------------------------------------------------------------------------
     // surface creation / destruction
 
@@ -329,6 +335,7 @@
         Transaction& setCrop(const sp<SurfaceControl>& sc, const Rect& crop);
         Transaction& setFrame(const sp<SurfaceControl>& sc, const Rect& frame);
         Transaction& setBuffer(const sp<SurfaceControl>& sc, const sp<GraphicBuffer>& buffer);
+        Transaction& setCachedBuffer(const sp<SurfaceControl>& sc, int32_t bufferId);
         Transaction& setAcquireFence(const sp<SurfaceControl>& sc, const sp<Fence>& fence);
         Transaction& setDataspace(const sp<SurfaceControl>& sc, ui::Dataspace dataspace);
         Transaction& setHdrMetadata(const sp<SurfaceControl>& sc, const HdrMetadata& hdrMetadata);
diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp
index 1d2950a..259ef9f 100644
--- a/libs/gui/tests/Surface_test.cpp
+++ b/libs/gui/tests/Surface_test.cpp
@@ -660,6 +660,12 @@
     status_t getColorManagement(bool* /*outGetColorManagement*/) const override { return NO_ERROR; }
     status_t getProtectedContentSupport(bool* /*outSupported*/) const override { return NO_ERROR; }
 
+    status_t cacheBuffer(const sp<IBinder>& /*token*/, const sp<GraphicBuffer>& /*buffer*/,
+                         int32_t* /*outBufferId*/) {
+        return NO_ERROR;
+    }
+    status_t uncacheBuffer(const sp<IBinder>& /*token*/, int32_t /*bufferId*/) { return NO_ERROR; }
+
 protected:
     IBinder* onAsBinder() override { return nullptr; }
 
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index 5b3bbca..033ed66 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -114,6 +114,7 @@
         "BufferLayerConsumer.cpp",
         "BufferQueueLayer.cpp",
         "BufferStateLayer.cpp",
+        "BufferStateLayerCache.cpp",
         "Client.cpp",
         "ColorLayer.cpp",
         "ContainerLayer.cpp",
diff --git a/services/surfaceflinger/BufferStateLayerCache.cpp b/services/surfaceflinger/BufferStateLayerCache.cpp
new file mode 100644
index 0000000..c82ad7b
--- /dev/null
+++ b/services/surfaceflinger/BufferStateLayerCache.cpp
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#undef LOG_TAG
+#define LOG_TAG "BufferStateLayerCache"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
+#include "BufferStateLayerCache.h"
+
+#define MAX_CACHE_SIZE 64
+
+namespace android {
+
+int32_t BufferStateLayerCache::add(const sp<IBinder>& processToken,
+                                   const sp<GraphicBuffer>& buffer) {
+    std::lock_guard lock(mMutex);
+
+    auto& processCache = getProccessCache(processToken);
+
+    int32_t slot = findSlot(processCache);
+    if (slot < 0) {
+        return slot;
+    }
+
+    processCache[slot] = buffer;
+
+    return slot;
+}
+
+void BufferStateLayerCache::release(const sp<IBinder>& processToken, int32_t id) {
+    if (id < 0) {
+        ALOGE("invalid buffer id");
+        return;
+    }
+
+    std::lock_guard lock(mMutex);
+    auto& processCache = getProccessCache(processToken);
+
+    if (id >= processCache.size()) {
+        ALOGE("invalid buffer id");
+        return;
+    }
+    processCache[id] = nullptr;
+}
+
+sp<GraphicBuffer> BufferStateLayerCache::get(const sp<IBinder>& processToken, int32_t id) {
+    if (id < 0) {
+        ALOGE("invalid buffer id");
+        return nullptr;
+    }
+
+    std::lock_guard lock(mMutex);
+    auto& processCache = getProccessCache(processToken);
+
+    if (id >= processCache.size()) {
+        ALOGE("invalid buffer id");
+        return nullptr;
+    }
+    return processCache[id];
+}
+
+std::vector<sp<GraphicBuffer>>& BufferStateLayerCache::getProccessCache(
+        const sp<IBinder>& processToken) {
+    return mBuffers[processToken];
+}
+
+int32_t BufferStateLayerCache::findSlot(std::vector<sp<GraphicBuffer>>& processCache) {
+    int32_t slot = 0;
+
+    for (const sp<GraphicBuffer> buffer : processCache) {
+        if (!buffer) {
+            return slot;
+        }
+        slot++;
+    }
+
+    if (processCache.size() < MAX_CACHE_SIZE) {
+        processCache.push_back(nullptr);
+        return slot;
+    }
+
+    return -1;
+}
+
+}; // namespace android
diff --git a/services/surfaceflinger/BufferStateLayerCache.h b/services/surfaceflinger/BufferStateLayerCache.h
new file mode 100644
index 0000000..623f0c6
--- /dev/null
+++ b/services/surfaceflinger/BufferStateLayerCache.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2019 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 <android-base/thread_annotations.h>
+#include <binder/IBinder.h>
+#include <ui/GraphicBuffer.h>
+#include <utils/RefBase.h>
+
+#include <mutex>
+#include <unordered_map>
+#include <vector>
+
+namespace android {
+
+class BufferStateLayerCache {
+public:
+    int32_t add(const sp<IBinder>& processToken, const sp<GraphicBuffer>& buffer);
+    void release(const sp<IBinder>& processToken, int32_t id);
+
+    sp<GraphicBuffer> get(const sp<IBinder>& processToken, int32_t id);
+
+private:
+    std::mutex mMutex;
+
+    std::vector<sp<GraphicBuffer>>& getProccessCache(const sp<IBinder>& processToken)
+            REQUIRES(mMutex);
+
+    int32_t findSlot(std::vector<sp<GraphicBuffer>>& proccessCache) REQUIRES(mMutex);
+
+    struct IBinderHash {
+        std::size_t operator()(const sp<IBinder>& strongPointer) const {
+            return std::hash<IBinder*>{}(strongPointer.get());
+        }
+    };
+
+    std::unordered_map<sp<IBinder> /*caching process*/, std::vector<sp<GraphicBuffer>>, IBinderHash>
+            mBuffers GUARDED_BY(mMutex);
+};
+
+}; // namespace android
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index c0059d5..2640625 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -1160,6 +1160,20 @@
     return NO_ERROR;
 }
 
+status_t SurfaceFlinger::cacheBuffer(const sp<IBinder>& token, const sp<GraphicBuffer>& buffer,
+                                     int32_t* outBufferId) {
+    if (!outBufferId) {
+        return BAD_VALUE;
+    }
+    *outBufferId = mBufferStateLayerCache.add(token, buffer);
+    return NO_ERROR;
+}
+
+status_t SurfaceFlinger::uncacheBuffer(const sp<IBinder>& token, int32_t bufferId) {
+    mBufferStateLayerCache.release(token, bufferId);
+    return NO_ERROR;
+}
+
 status_t SurfaceFlinger::enableVSyncInjections(bool enable) {
     postMessageSync(new LambdaMessage([&] {
         Mutex::Autolock _l(mStateLock);
@@ -3932,6 +3946,11 @@
             callbackHandles.emplace_back(new CallbackHandle(listener, callbackIds, s.surface));
         }
     }
+    if (what & layer_state_t::eCachedBufferChanged) {
+        sp<GraphicBuffer> buffer =
+                mBufferStateLayerCache.get(s.cachedBuffer.token, s.cachedBuffer.bufferId);
+        if (layer->setBuffer(buffer)) flags |= eTraversalNeeded;
+    }
     if (layer->setTransactionCompletedListeners(callbackHandles)) flags |= eTraversalNeeded;
     // Do not put anything that updates layer state or modifies flags after
     // setTransactionCompletedListener
@@ -5025,7 +5044,9 @@
         case CREATE_CONNECTION:
         case GET_COLOR_MANAGEMENT:
         case GET_COMPOSITION_PREFERENCE:
-        case GET_PROTECTED_CONTENT_SUPPORT: {
+        case GET_PROTECTED_CONTENT_SUPPORT:
+        case CACHE_BUFFER:
+        case UNCACHE_BUFFER: {
             return OK;
         }
         case CAPTURE_LAYERS:
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index e5b3570..1e6db06 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -48,6 +48,7 @@
 #include <utils/threads.h>
 
 #include "Barrier.h"
+#include "BufferStateLayerCache.h"
 #include "DisplayDevice.h"
 #include "DisplayHardware/HWC2.h"
 #include "DisplayHardware/HWComposer.h"
@@ -459,6 +460,9 @@
                                        uint64_t timestamp,
                                        DisplayedFrameStats* outStats) const override;
     status_t getProtectedContentSupport(bool* outSupported) const override;
+    status_t cacheBuffer(const sp<IBinder>& token, const sp<GraphicBuffer>& buffer,
+                         int32_t* outBufferId) override;
+    status_t uncacheBuffer(const sp<IBinder>& token, int32_t bufferId) override;
 
     /* ------------------------------------------------------------------------
      * DeathRecipient interface
@@ -1034,6 +1038,8 @@
     sp<IInputFlinger> mInputFlinger;
 
     InputWindowCommands mInputWindowCommands;
+
+    BufferStateLayerCache mBufferStateLayerCache;
 };
 }; // namespace android
 
diff --git a/services/surfaceflinger/tests/Transaction_test.cpp b/services/surfaceflinger/tests/Transaction_test.cpp
index 0ad2711..216532a 100644
--- a/services/surfaceflinger/tests/Transaction_test.cpp
+++ b/services/surfaceflinger/tests/Transaction_test.cpp
@@ -2323,6 +2323,143 @@
     Transaction().setSidebandStream(layer, nullptr).apply();
 }
 
+TEST_F(LayerTransactionTest, CacheBuffer_BufferState) {
+    sp<GraphicBuffer> buffer =
+            new GraphicBuffer(32, 32, PIXEL_FORMAT_RGBA_8888, 1,
+                              BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN |
+                                      BufferUsage::COMPOSER_OVERLAY,
+                              "test");
+
+    int32_t bufferId = -1;
+    ASSERT_EQ(NO_ERROR, mClient->cacheBuffer(buffer, &bufferId));
+    ASSERT_GE(bufferId, 0);
+
+    ASSERT_EQ(NO_ERROR, mClient->uncacheBuffer(bufferId));
+}
+
+TEST_F(LayerTransactionTest, CacheBuffers_BufferState) {
+    std::vector<int32_t> bufferIds;
+    int32_t bufferCount = 20;
+
+    for (int i = 0; i < bufferCount; i++) {
+        sp<GraphicBuffer> buffer =
+                new GraphicBuffer(32, 32, PIXEL_FORMAT_RGBA_8888, 1,
+                                  BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN |
+                                          BufferUsage::COMPOSER_OVERLAY,
+                                  "test");
+        int32_t bufferId = -1;
+        ASSERT_EQ(NO_ERROR, mClient->cacheBuffer(buffer, &bufferId));
+        if (bufferId < 0) {
+            EXPECT_GE(bufferId, 0);
+            break;
+        }
+        bufferIds.push_back(bufferId);
+    }
+
+    for (int32_t bufferId : bufferIds) {
+        ASSERT_EQ(NO_ERROR, mClient->uncacheBuffer(bufferId));
+    }
+}
+
+TEST_F(LayerTransactionTest, CacheBufferInvalid_BufferState) {
+    sp<GraphicBuffer> buffer = nullptr;
+
+    int32_t bufferId = -1;
+    ASSERT_NE(NO_ERROR, mClient->cacheBuffer(buffer, &bufferId));
+    ASSERT_LT(bufferId, 0);
+}
+
+TEST_F(LayerTransactionTest, UncacheBufferTwice_BufferState) {
+    sp<GraphicBuffer> buffer =
+            new GraphicBuffer(32, 32, PIXEL_FORMAT_RGBA_8888, 1,
+                              BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN |
+                                      BufferUsage::COMPOSER_OVERLAY,
+                              "test");
+
+    int32_t bufferId = -1;
+    ASSERT_EQ(NO_ERROR, mClient->cacheBuffer(buffer, &bufferId));
+    ASSERT_GE(bufferId, 0);
+
+    ASSERT_EQ(NO_ERROR, mClient->uncacheBuffer(bufferId));
+    mClient->uncacheBuffer(bufferId);
+}
+
+TEST_F(LayerTransactionTest, UncacheBufferInvalidId_BufferState) {
+    mClient->uncacheBuffer(-1);
+    mClient->uncacheBuffer(0);
+    mClient->uncacheBuffer(1);
+    mClient->uncacheBuffer(5);
+    mClient->uncacheBuffer(1000);
+}
+
+TEST_F(LayerTransactionTest, SetCachedBuffer_BufferState) {
+    sp<SurfaceControl> layer;
+    ASSERT_NO_FATAL_FAILURE(
+            layer = createLayer("test", 32, 32, ISurfaceComposerClient::eFXSurfaceBufferState));
+
+    sp<GraphicBuffer> buffer =
+            new GraphicBuffer(32, 32, PIXEL_FORMAT_RGBA_8888, 1,
+                              BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN |
+                                      BufferUsage::COMPOSER_OVERLAY,
+                              "test");
+
+    fillGraphicBufferColor(buffer, Rect(0, 0, 32, 32), Color::RED);
+
+    int32_t bufferId = -1;
+    ASSERT_EQ(NO_ERROR, mClient->cacheBuffer(buffer, &bufferId));
+    ASSERT_GE(bufferId, 0);
+
+    Transaction().setCachedBuffer(layer, bufferId).apply();
+
+    auto shot = screenshot();
+    shot->expectColor(Rect(0, 0, 32, 32), Color::RED);
+    shot->expectBorder(Rect(0, 0, 32, 32), Color::BLACK);
+
+    ASSERT_EQ(NO_ERROR, mClient->uncacheBuffer(bufferId));
+}
+
+TEST_F(LayerTransactionTest, SetCachedBufferDelayed_BufferState) {
+    sp<SurfaceControl> layer;
+    ASSERT_NO_FATAL_FAILURE(
+            layer = createLayer("test", 32, 32, ISurfaceComposerClient::eFXSurfaceBufferState));
+
+    sp<GraphicBuffer> cachedBuffer =
+            new GraphicBuffer(32, 32, PIXEL_FORMAT_RGBA_8888, 1,
+                              BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN |
+                                      BufferUsage::COMPOSER_OVERLAY,
+                              "test");
+    int32_t bufferId = -1;
+    ASSERT_EQ(NO_ERROR, mClient->cacheBuffer(cachedBuffer, &bufferId));
+    ASSERT_GE(bufferId, 0);
+
+    sp<GraphicBuffer> buffer =
+            new GraphicBuffer(32, 32, PIXEL_FORMAT_RGBA_8888, 1,
+                              BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN |
+                                      BufferUsage::COMPOSER_OVERLAY,
+                              "test");
+    fillGraphicBufferColor(buffer, Rect(0, 0, 32, 32), Color::BLUE);
+    Transaction().setBuffer(layer, buffer).apply();
+    {
+        SCOPED_TRACE("Uncached buffer");
+
+        auto shot = screenshot();
+        shot->expectColor(Rect(0, 0, 32, 32), Color::BLUE);
+        shot->expectBorder(Rect(0, 0, 32, 32), Color::BLACK);
+    }
+
+    fillGraphicBufferColor(cachedBuffer, Rect(0, 0, 32, 32), Color::RED);
+    Transaction().setCachedBuffer(layer, bufferId).apply();
+    {
+        SCOPED_TRACE("Cached buffer");
+
+        auto shot = screenshot();
+        shot->expectColor(Rect(0, 0, 32, 32), Color::RED);
+        shot->expectBorder(Rect(0, 0, 32, 32), Color::BLACK);
+    }
+
+    ASSERT_EQ(NO_ERROR, mClient->uncacheBuffer(bufferId));
+}
+
 class ColorTransformHelper {
 public:
     static void DegammaColorSingle(half& s) {