Defer deleting ExternalTextures that go out of scope during DrawLayers

Some buffers (e.g. protected content) are not pre-mapped into a
texture.  Their textures are created in drawLayers and go out of scope
at the end of drawLayers drawLayers when those temporary textures are
sent to the GPU.

This CL ensures that in those cases we defer unbinding and deleting the
temporary texture until the cleanupPostRender method is called. Also to
avoid unecessary thread hops into cleanupPostRender this CL also adds a
thread-safe check to see if cleanup is necessary. Finally, we also make
the threaded variant asynchronous to further improve the availability of
SurfaceFlinger.

Bug: 190628682
Bug: 191132989
Test: atest librenderengine_test and perfetto traces
Change-Id: Ic2f4384cd1957c928a0ef656a98eb0041e29622c
diff --git a/libs/renderengine/gl/GLESRenderEngine.cpp b/libs/renderengine/gl/GLESRenderEngine.cpp
index 18eb7b5..b5dd8ac 100644
--- a/libs/renderengine/gl/GLESRenderEngine.cpp
+++ b/libs/renderengine/gl/GLESRenderEngine.cpp
@@ -970,37 +970,17 @@
     glBindFramebuffer(GL_FRAMEBUFFER, 0);
 }
 
-bool GLESRenderEngine::cleanupPostRender(CleanupMode mode) {
+bool GLESRenderEngine::canSkipPostRenderCleanup() const {
+    return mPriorResourcesCleaned ||
+            (mLastDrawFence != nullptr && mLastDrawFence->getStatus() != Fence::Status::Signaled);
+}
+
+void GLESRenderEngine::cleanupPostRender() {
     ATRACE_CALL();
 
-    if (mPriorResourcesCleaned ||
-        (mLastDrawFence != nullptr && mLastDrawFence->getStatus() != Fence::Status::Signaled)) {
+    if (canSkipPostRenderCleanup()) {
         // If we don't have a prior frame needing cleanup, then don't do anything.
-        return false;
-    }
-
-    // This is a bit of a band-aid fix for FrameCaptureProcessor, as we should
-    // not need to keep memory around if we don't need to do so.
-    if (mode == CleanupMode::CLEAN_ALL) {
-        // TODO: SurfaceFlinger memory utilization may benefit from resetting
-        // texture bindings as well. Assess if it does and there's no performance regression
-        // when rebinding the same image data to the same texture, and if so then its mode
-        // behavior can be tweaked.
-        if (mPlaceholderImage != EGL_NO_IMAGE_KHR) {
-            for (auto [textureName, bufferId] : mTextureView) {
-                if (bufferId && mPlaceholderImage != EGL_NO_IMAGE_KHR) {
-                    glBindTexture(GL_TEXTURE_EXTERNAL_OES, textureName);
-                    glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES,
-                                                 static_cast<GLeglImageOES>(mPlaceholderImage));
-                    mTextureView[textureName] = std::nullopt;
-                    checkErrors();
-                }
-            }
-        }
-        {
-            std::lock_guard<std::mutex> lock(mRenderingMutex);
-            mImageCache.clear();
-        }
+        return;
     }
 
     // Bind the texture to placeholder so that backing image data can be freed.
@@ -1011,7 +991,6 @@
     // we could no-op repeated calls of this method instead.
     mLastDrawFence = nullptr;
     mPriorResourcesCleaned = true;
-    return true;
 }
 
 void GLESRenderEngine::cleanFramebufferCache() {
diff --git a/libs/renderengine/gl/GLESRenderEngine.h b/libs/renderengine/gl/GLESRenderEngine.h
index 1ff3674..915dba3 100644
--- a/libs/renderengine/gl/GLESRenderEngine.h
+++ b/libs/renderengine/gl/GLESRenderEngine.h
@@ -68,7 +68,7 @@
                         const std::shared_ptr<ExternalTexture>& buffer,
                         const bool useFramebufferCache, base::unique_fd&& bufferFence,
                         base::unique_fd* drawFence) override;
-    bool cleanupPostRender(CleanupMode mode) override;
+    void cleanupPostRender() override;
     int getContextPriority() override;
     bool supportsBackgroundBlur() override { return mBlurFilter != nullptr; }
     void onPrimaryDisplaySizeChanged(ui::Size size) override {}
@@ -106,6 +106,7 @@
     void mapExternalTextureBuffer(const sp<GraphicBuffer>& buffer, bool isRenderable)
             EXCLUDES(mRenderingMutex);
     void unmapExternalTextureBuffer(const sp<GraphicBuffer>& buffer) EXCLUDES(mRenderingMutex);
+    bool canSkipPostRenderCleanup() const override;
 
 private:
     friend class BindNativeBufferAsFramebuffer;
diff --git a/libs/renderengine/include/renderengine/RenderEngine.h b/libs/renderengine/include/renderengine/RenderEngine.h
index 1da9adc..ac0affb 100644
--- a/libs/renderengine/include/renderengine/RenderEngine.h
+++ b/libs/renderengine/include/renderengine/RenderEngine.h
@@ -114,25 +114,6 @@
     virtual void genTextures(size_t count, uint32_t* names) = 0;
     virtual void deleteTextures(size_t count, uint32_t const* names) = 0;
 
-    enum class CleanupMode {
-        CLEAN_OUTPUT_RESOURCES,
-        CLEAN_ALL,
-    };
-    // Clean-up method that should be called on the main thread after the
-    // drawFence returned by drawLayers fires. This method will free up
-    // resources used by the most recently drawn frame. If the frame is still
-    // being drawn, then this call is silently ignored.
-    //
-    // If mode is CLEAN_OUTPUT_RESOURCES, then only resources related to the
-    // output framebuffer are cleaned up, including the sibling texture.
-    //
-    // If mode is CLEAN_ALL, then we also cleanup resources related to any input
-    // buffers.
-    //
-    // Returns true if resources were cleaned up, and false if we didn't need to
-    // do any work.
-    virtual bool cleanupPostRender(CleanupMode mode = CleanupMode::CLEAN_OUTPUT_RESOURCES) = 0;
-
     // queries that are required to be thread safe
     virtual size_t getMaxTextureSize() const = 0;
     virtual size_t getMaxViewportDims() const = 0;
@@ -186,6 +167,13 @@
                                 const std::shared_ptr<ExternalTexture>& buffer,
                                 const bool useFramebufferCache, base::unique_fd&& bufferFence,
                                 base::unique_fd* drawFence) = 0;
+
+    // Clean-up method that should be called on the main thread after the
+    // drawFence returned by drawLayers fires. This method will free up
+    // resources used by the most recently drawn frame. If the frame is still
+    // being drawn, then the implementation is free to silently ignore this call.
+    virtual void cleanupPostRender() = 0;
+
     virtual void cleanFramebufferCache() = 0;
     // Returns the priority this context was actually created with. Note: this may not be
     // the same as specified at context creation time, due to implementation limits on the
@@ -234,8 +222,15 @@
     // that's conflict serializable, i.e. unmap a buffer should never occur before binding the
     // buffer if the caller called mapExternalTextureBuffer before calling unmap.
     virtual void unmapExternalTextureBuffer(const sp<GraphicBuffer>& buffer) = 0;
+
+    // A thread safe query to determine if any post rendering cleanup is necessary.  Returning true
+    // is a signal that calling the postRenderCleanup method would be a no-op and that callers can
+    // avoid any thread synchronization that may be required by directly calling postRenderCleanup.
+    virtual bool canSkipPostRenderCleanup() const = 0;
+
     friend class ExternalTexture;
     friend class threaded::RenderEngineThreaded;
+    friend class RenderEngineTest_cleanupPostRender_cleansUpOnce_Test;
     const RenderEngineType mRenderEngineType;
 };
 
diff --git a/libs/renderengine/include/renderengine/mock/RenderEngine.h b/libs/renderengine/include/renderengine/mock/RenderEngine.h
index baa1305..0175af3 100644
--- a/libs/renderengine/include/renderengine/mock/RenderEngine.h
+++ b/libs/renderengine/include/renderengine/mock/RenderEngine.h
@@ -45,7 +45,8 @@
     MOCK_CONST_METHOD0(isProtected, bool());
     MOCK_CONST_METHOD0(supportsProtectedContent, bool());
     MOCK_METHOD1(useProtectedContext, bool(bool));
-    MOCK_METHOD1(cleanupPostRender, bool(CleanupMode mode));
+    MOCK_METHOD0(cleanupPostRender, void());
+    MOCK_CONST_METHOD0(canSkipPostRenderCleanup, bool());
     MOCK_METHOD6(drawLayers,
                  status_t(const DisplaySettings&, const std::vector<const LayerSettings*>&,
                           const std::shared_ptr<ExternalTexture>&, const bool, base::unique_fd&&,
diff --git a/libs/renderengine/skia/AutoBackendTexture.cpp b/libs/renderengine/skia/AutoBackendTexture.cpp
index 8356005..5c122d4 100644
--- a/libs/renderengine/skia/AutoBackendTexture.cpp
+++ b/libs/renderengine/skia/AutoBackendTexture.cpp
@@ -29,8 +29,8 @@
 namespace skia {
 
 AutoBackendTexture::AutoBackendTexture(GrDirectContext* context, AHardwareBuffer* buffer,
-                                       bool isOutputBuffer)
-      : mIsOutputBuffer(isOutputBuffer) {
+                                       bool isOutputBuffer, CleanupManager& cleanupMgr)
+      : mCleanupMgr(cleanupMgr), mIsOutputBuffer(isOutputBuffer) {
     ATRACE_CALL();
     AHardwareBuffer_Desc desc;
     AHardwareBuffer_describe(buffer, &desc);
@@ -49,6 +49,13 @@
              this, desc.width, desc.height, createProtectedImage, isOutputBuffer, desc.format);
 }
 
+AutoBackendTexture::~AutoBackendTexture() {
+    if (mBackendTexture.isValid()) {
+        mDeleteProc(mImageCtx);
+        mBackendTexture = {};
+    }
+}
+
 void AutoBackendTexture::unref(bool releaseLocalResources) {
     if (releaseLocalResources) {
         mSurface = nullptr;
@@ -57,11 +64,7 @@
 
     mUsageCount--;
     if (mUsageCount <= 0) {
-        if (mBackendTexture.isValid()) {
-            mDeleteProc(mImageCtx);
-            mBackendTexture = {};
-        }
-        delete this;
+        mCleanupMgr.add(this);
     }
 }
 
diff --git a/libs/renderengine/skia/AutoBackendTexture.h b/libs/renderengine/skia/AutoBackendTexture.h
index a9e8430..00b901b 100644
--- a/libs/renderengine/skia/AutoBackendTexture.h
+++ b/libs/renderengine/skia/AutoBackendTexture.h
@@ -25,6 +25,9 @@
 
 #include "android-base/macros.h"
 
+#include <mutex>
+#include <vector>
+
 namespace android {
 namespace renderengine {
 namespace skia {
@@ -36,13 +39,50 @@
  */
 class AutoBackendTexture {
 public:
+    // Manager class that is responsible for the immediate or deferred cleanup
+    // of AutoBackendTextures.  Clients of AutoBackendTexture are responsible for
+    // ensuring that access to this class is thread safe.  Clients also control when
+    // the resources are reclaimed by setting the manager into deferred mode.
+    class CleanupManager {
+    public:
+        CleanupManager() = default;
+        void add(AutoBackendTexture* abt) {
+            if (mDeferCleanup) {
+                mCleanupList.push_back(abt);
+            } else {
+                delete abt;
+            }
+        }
+
+        void setDeferredStatus(bool enabled) { mDeferCleanup = enabled; }
+
+        bool isEmpty() const { return mCleanupList.empty(); }
+
+        // If any AutoBackedTextures were added while in deferred mode this method
+        // will ensure they are deleted before returning.  It must only be called
+        // on the thread where the GPU context that created the AutoBackedTexture
+        // is active.
+        void cleanup() {
+            for (auto abt : mCleanupList) {
+                delete abt;
+            }
+            mCleanupList.clear();
+        }
+
+    private:
+        DISALLOW_COPY_AND_ASSIGN(CleanupManager);
+        bool mDeferCleanup = false;
+        std::vector<AutoBackendTexture*> mCleanupList;
+    };
+
     // Local reference that supports RAII-style management of an AutoBackendTexture
     // AutoBackendTexture by itself can't be managed in a similar fashion because
     // of shared ownership with Skia objects, so we wrap it here instead.
     class LocalRef {
     public:
-        LocalRef(GrDirectContext* context, AHardwareBuffer* buffer, bool isOutputBuffer) {
-            mTexture = new AutoBackendTexture(context, buffer, isOutputBuffer);
+        LocalRef(GrDirectContext* context, AHardwareBuffer* buffer, bool isOutputBuffer,
+                 CleanupManager& cleanupMgr) {
+            mTexture = new AutoBackendTexture(context, buffer, isOutputBuffer, cleanupMgr);
             mTexture->ref();
         }
 
@@ -75,10 +115,11 @@
 
 private:
     // Creates a GrBackendTexture whose contents come from the provided buffer.
-    AutoBackendTexture(GrDirectContext* context, AHardwareBuffer* buffer, bool isOutputBuffer);
+    AutoBackendTexture(GrDirectContext* context, AHardwareBuffer* buffer, bool isOutputBuffer,
+                       CleanupManager& cleanupMgr);
 
     // The only way to invoke dtor is with unref, when mUsageCount is 0.
-    ~AutoBackendTexture() {}
+    ~AutoBackendTexture();
 
     void ref() { mUsageCount++; }
 
@@ -100,6 +141,8 @@
     GrAHardwareBufferUtils::UpdateImageProc mUpdateProc;
     GrAHardwareBufferUtils::TexImageCtx mImageCtx;
 
+    CleanupManager& mCleanupMgr;
+
     static void releaseSurfaceProc(SkSurface::ReleaseContext releaseContext);
     static void releaseImageProc(SkImage::ReleaseContext releaseContext);
 
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
index f0986a3..d28d623 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
@@ -531,7 +531,7 @@
         std::shared_ptr<AutoBackendTexture::LocalRef> imageTextureRef =
                 std::make_shared<AutoBackendTexture::LocalRef>(grContext,
                                                                buffer->toAHardwareBuffer(),
-                                                               isRenderable);
+                                                               isRenderable, mTextureCleanupMgr);
         cache.insert({buffer->getId(), imageTextureRef});
     }
 }
@@ -559,6 +559,31 @@
     }
 }
 
+bool SkiaGLRenderEngine::canSkipPostRenderCleanup() const {
+    std::lock_guard<std::mutex> lock(mRenderingMutex);
+    return mTextureCleanupMgr.isEmpty();
+}
+
+void SkiaGLRenderEngine::cleanupPostRender() {
+    ATRACE_CALL();
+    std::lock_guard<std::mutex> lock(mRenderingMutex);
+    mTextureCleanupMgr.cleanup();
+}
+
+// Helper class intended to be used on the stack to ensure that texture cleanup
+// is deferred until after this class goes out of scope.
+class DeferTextureCleanup final {
+public:
+    DeferTextureCleanup(AutoBackendTexture::CleanupManager& mgr) : mMgr(mgr) {
+        mMgr.setDeferredStatus(true);
+    }
+    ~DeferTextureCleanup() { mMgr.setDeferredStatus(false); }
+
+private:
+    DISALLOW_COPY_AND_ASSIGN(DeferTextureCleanup);
+    AutoBackendTexture::CleanupManager& mMgr;
+};
+
 sk_sp<SkShader> SkiaGLRenderEngine::createRuntimeEffectShader(
         sk_sp<SkShader> shader,
         const LayerSettings* layer, const DisplaySettings& display, bool undoPremultipliedAlpha,
@@ -707,6 +732,9 @@
     auto grContext = getActiveGrContext();
     auto& cache = mTextureCache;
 
+    // any AutoBackendTexture deletions will now be deferred until cleanupPostRender is called
+    DeferTextureCleanup dtc(mTextureCleanupMgr);
+
     std::shared_ptr<AutoBackendTexture::LocalRef> surfaceTextureRef;
     if (const auto& it = cache.find(buffer->getBuffer()->getId()); it != cache.end()) {
         surfaceTextureRef = it->second;
@@ -715,7 +743,7 @@
                 std::make_shared<AutoBackendTexture::LocalRef>(grContext,
                                                                buffer->getBuffer()
                                                                        ->toAHardwareBuffer(),
-                                                               true);
+                                                               true, mTextureCleanupMgr);
     }
 
     const ui::Dataspace dstDataspace =
@@ -971,7 +999,7 @@
                 imageTextureRef = std::make_shared<
                         AutoBackendTexture::LocalRef>(grContext,
                                                       item.buffer->getBuffer()->toAHardwareBuffer(),
-                                                      false);
+                                                      false, mTextureCleanupMgr);
             }
 
             // isOpaque means we need to ignore the alpha in the image,
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.h b/libs/renderengine/skia/SkiaGLRenderEngine.h
index b972c73..b30355b 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.h
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.h
@@ -59,7 +59,8 @@
                         const std::shared_ptr<ExternalTexture>& buffer,
                         const bool useFramebufferCache, base::unique_fd&& bufferFence,
                         base::unique_fd* drawFence) override;
-    void cleanFramebufferCache() override {}
+    void cleanupPostRender() override;
+    void cleanFramebufferCache() override{};
     int getContextPriority() override;
     bool isProtected() const override { return mInProtectedContext; }
     bool supportsProtectedContent() const override;
@@ -75,6 +76,7 @@
     size_t getMaxViewportDims() const override;
     void mapExternalTextureBuffer(const sp<GraphicBuffer>& buffer, bool isRenderable) override;
     void unmapExternalTextureBuffer(const sp<GraphicBuffer>& buffer) override;
+    bool canSkipPostRenderCleanup() const override;
 
 private:
     static EGLConfig chooseEglConfig(EGLDisplay display, int format, bool logConfig);
@@ -130,13 +132,14 @@
     std::unordered_map<GraphicBufferId, std::shared_ptr<AutoBackendTexture::LocalRef>> mTextureCache
             GUARDED_BY(mRenderingMutex);
     std::unordered_map<LinearEffect, sk_sp<SkRuntimeEffect>, LinearEffectHasher> mRuntimeEffects;
+    AutoBackendTexture::CleanupManager mTextureCleanupMgr GUARDED_BY(mRenderingMutex);
 
     StretchShaderFactory mStretchShaderFactory;
     // Mutex guarding rendering operations, so that:
     // 1. GL operations aren't interleaved, and
     // 2. Internal state related to rendering that is potentially modified by
     // multiple threads is guaranteed thread-safe.
-    std::mutex mRenderingMutex;
+    mutable std::mutex mRenderingMutex;
 
     sp<Fence> mLastDrawFence;
 
diff --git a/libs/renderengine/skia/SkiaRenderEngine.h b/libs/renderengine/skia/SkiaRenderEngine.h
index f098a86..31ad63e 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.h
+++ b/libs/renderengine/skia/SkiaRenderEngine.h
@@ -53,7 +53,6 @@
                                 base::unique_fd* /*drawFence*/) override {
         return 0;
     };
-    virtual bool cleanupPostRender(CleanupMode) override { return true; };
     virtual int getContextPriority() override { return 0; }
     virtual void assertShadersCompiled(int numShaders) {}
     virtual int reportShadersCompiled() { return 0; }
diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp
index dfab6e8..e258741 100644
--- a/libs/renderengine/tests/RenderEngineTest.cpp
+++ b/libs/renderengine/tests/RenderEngineTest.cpp
@@ -43,6 +43,7 @@
 constexpr bool WRITE_BUFFER_TO_FILE_ON_FAILURE = false;
 
 namespace android {
+namespace renderengine {
 
 class RenderEngineFactory {
 public:
@@ -1779,13 +1780,6 @@
 }
 
 TEST_P(RenderEngineTest, cleanupPostRender_cleansUpOnce) {
-    const auto& renderEngineFactory = GetParam();
-
-    if (renderEngineFactory->type() != renderengine::RenderEngine::RenderEngineType::GLES) {
-        // GLES-specific test
-        return;
-    }
-
     initializeRenderEngine();
 
     renderengine::DisplaySettings settings;
@@ -1810,53 +1804,9 @@
         sync_wait(fd, -1);
     }
     // Only cleanup the first time.
-    EXPECT_TRUE(mRE->cleanupPostRender(
-            renderengine::RenderEngine::CleanupMode::CLEAN_OUTPUT_RESOURCES));
-    EXPECT_FALSE(mRE->cleanupPostRender(
-            renderengine::RenderEngine::CleanupMode::CLEAN_OUTPUT_RESOURCES));
-}
-
-TEST_P(RenderEngineTest, cleanupPostRender_whenCleaningAll_replacesTextureMemory) {
-    const auto& renderEngineFactory = GetParam();
-
-    if (renderEngineFactory->type() != renderengine::RenderEngine::RenderEngineType::GLES) {
-        // GLES-specific test
-        return;
-    }
-
-    initializeRenderEngine();
-
-    renderengine::DisplaySettings settings;
-    settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR;
-    settings.physicalDisplay = fullscreenRect();
-    settings.clip = fullscreenRect();
-
-    std::vector<const renderengine::LayerSettings*> layers;
-    renderengine::LayerSettings layer;
-    layer.geometry.boundaries = fullscreenRect().toFloatRect();
-    BufferSourceVariant<ForceOpaqueBufferVariant>::fillColor(layer, 1.0f, 0.0f, 0.0f, this);
-    layer.alpha = 1.0;
-    layers.push_back(&layer);
-
-    base::unique_fd fence;
-    mRE->drawLayers(settings, layers, mBuffer, true, base::unique_fd(), &fence);
-
-    const int fd = fence.get();
-    if (fd >= 0) {
-        sync_wait(fd, -1);
-    }
-
-    uint64_t bufferId = layer.source.buffer.buffer->getBuffer()->getId();
-    uint32_t texName = layer.source.buffer.textureName;
-    EXPECT_TRUE(mGLESRE->isImageCachedForTesting(bufferId));
-    EXPECT_EQ(bufferId, mGLESRE->getBufferIdForTextureNameForTesting(texName));
-
-    EXPECT_TRUE(mRE->cleanupPostRender(renderengine::RenderEngine::CleanupMode::CLEAN_ALL));
-
-    // Now check that our view of memory is good.
-    EXPECT_FALSE(mGLESRE->isImageCachedForTesting(bufferId));
-    EXPECT_EQ(std::nullopt, mGLESRE->getBufferIdForTextureNameForTesting(bufferId));
-    EXPECT_TRUE(mGLESRE->isTextureNameKnownForTesting(texName));
+    EXPECT_FALSE(mRE->canSkipPostRenderCleanup());
+    mRE->cleanupPostRender();
+    EXPECT_TRUE(mRE->canSkipPostRenderCleanup());
 }
 
 TEST_P(RenderEngineTest, testRoundedCornersCrop) {
@@ -2080,6 +2030,7 @@
         expectBufferColor(rect, 0, 255, 0, 255);
     }
 }
+} // namespace renderengine
 } // namespace android
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
diff --git a/libs/renderengine/tests/RenderEngineThreadedTest.cpp b/libs/renderengine/tests/RenderEngineThreadedTest.cpp
index e3917cc..c65e731 100644
--- a/libs/renderengine/tests/RenderEngineThreadedTest.cpp
+++ b/libs/renderengine/tests/RenderEngineThreadedTest.cpp
@@ -130,22 +130,22 @@
     ASSERT_EQ(true, result);
 }
 
-TEST_F(RenderEngineThreadedTest, cleanupPostRender_returnsFalse) {
-    EXPECT_CALL(*mRenderEngine,
-                cleanupPostRender(renderengine::RenderEngine::CleanupMode::CLEAN_ALL))
-            .WillOnce(Return(false));
-    status_t result =
-            mThreadedRE->cleanupPostRender(renderengine::RenderEngine::CleanupMode::CLEAN_ALL);
-    ASSERT_EQ(false, result);
+TEST_F(RenderEngineThreadedTest, PostRenderCleanup_skipped) {
+    EXPECT_CALL(*mRenderEngine, canSkipPostRenderCleanup()).WillOnce(Return(true));
+    EXPECT_CALL(*mRenderEngine, cleanupPostRender()).Times(0);
+    mThreadedRE->cleanupPostRender();
+
+    // call ANY synchronous function to ensure that cleanupPostRender has completed.
+    mThreadedRE->getContextPriority();
 }
 
-TEST_F(RenderEngineThreadedTest, cleanupPostRender_returnsTrue) {
-    EXPECT_CALL(*mRenderEngine,
-                cleanupPostRender(renderengine::RenderEngine::CleanupMode::CLEAN_ALL))
-            .WillOnce(Return(true));
-    status_t result =
-            mThreadedRE->cleanupPostRender(renderengine::RenderEngine::CleanupMode::CLEAN_ALL);
-    ASSERT_EQ(true, result);
+TEST_F(RenderEngineThreadedTest, PostRenderCleanup_notSkipped) {
+    EXPECT_CALL(*mRenderEngine, canSkipPostRenderCleanup()).WillOnce(Return(false));
+    EXPECT_CALL(*mRenderEngine, cleanupPostRender()).WillOnce(Return());
+    mThreadedRE->cleanupPostRender();
+
+    // call ANY synchronous function to ensure that cleanupPostRender has completed.
+    mThreadedRE->getContextPriority();
 }
 
 TEST_F(RenderEngineThreadedTest, supportsBackgroundBlur_returnsFalse) {
diff --git a/libs/renderengine/threaded/RenderEngineThreaded.cpp b/libs/renderengine/threaded/RenderEngineThreaded.cpp
index 74c5f47..072b25c 100644
--- a/libs/renderengine/threaded/RenderEngineThreaded.cpp
+++ b/libs/renderengine/threaded/RenderEngineThreaded.cpp
@@ -270,19 +270,26 @@
     return resultFuture.get();
 }
 
-bool RenderEngineThreaded::cleanupPostRender(CleanupMode mode) {
-    std::promise<bool> resultPromise;
-    std::future<bool> resultFuture = resultPromise.get_future();
+void RenderEngineThreaded::cleanupPostRender() {
+    if (canSkipPostRenderCleanup()) {
+        return;
+    }
+
+    // This function is designed so it can run asynchronously, so we do not need to wait
+    // for the futures.
     {
         std::lock_guard lock(mThreadMutex);
-        mFunctionCalls.push([&resultPromise, mode](renderengine::RenderEngine& instance) {
-            ATRACE_NAME("REThreaded::cleanupPostRender");
-            bool returnValue = instance.cleanupPostRender(mode);
-            resultPromise.set_value(returnValue);
+        mFunctionCalls.push([=](renderengine::RenderEngine& instance) {
+            ATRACE_NAME("REThreaded::unmapExternalTextureBuffer");
+            instance.cleanupPostRender();
         });
     }
     mCondition.notify_one();
-    return resultFuture.get();
+}
+
+bool RenderEngineThreaded::canSkipPostRenderCleanup() const {
+    waitUntilInitialized();
+    return mRenderEngine->canSkipPostRenderCleanup();
 }
 
 status_t RenderEngineThreaded::drawLayers(const DisplaySettings& display,
diff --git a/libs/renderengine/threaded/RenderEngineThreaded.h b/libs/renderengine/threaded/RenderEngineThreaded.h
index c81f137..9b523b2 100644
--- a/libs/renderengine/threaded/RenderEngineThreaded.h
+++ b/libs/renderengine/threaded/RenderEngineThreaded.h
@@ -54,7 +54,7 @@
     bool isProtected() const override;
     bool supportsProtectedContent() const override;
     bool useProtectedContext(bool useProtectedContext) override;
-    bool cleanupPostRender(CleanupMode mode) override;
+    void cleanupPostRender() override;
 
     status_t drawLayers(const DisplaySettings& display,
                         const std::vector<const LayerSettings*>& layers,
@@ -70,6 +70,7 @@
 protected:
     void mapExternalTextureBuffer(const sp<GraphicBuffer>& buffer, bool isRenderable) override;
     void unmapExternalTextureBuffer(const sp<GraphicBuffer>& buffer) override;
+    bool canSkipPostRenderCleanup() const override;
 
 private:
     void threadMain(CreateInstanceFactory factory);