Merge "Cache blur shaders only when blur is enabled" into sc-dev
diff --git a/cmds/installd/InstalldNativeService.cpp b/cmds/installd/InstalldNativeService.cpp
index 818804a..0595322 100644
--- a/cmds/installd/InstalldNativeService.cpp
+++ b/cmds/installd/InstalldNativeService.cpp
@@ -342,7 +342,8 @@
 
     // If the initial top-level restorecon above changed the label, then go
     // back and restorecon everything recursively
-    if (strcmp(before, after)) {
+    // TODO(b/190567190, b/188141923) Remove recursive fixup of com.google.android.gsf.
+    if (strcmp(before, after) || (path.find("com.google.android.gsf") != std::string::npos)) {
         if (existing) {
             LOG(DEBUG) << "Detected label change from " << before << " to " << after << " at "
                     << path << "; running recursive restorecon";
diff --git a/cmds/installd/dexopt.cpp b/cmds/installd/dexopt.cpp
index 204953c..cc0434d 100644
--- a/cmds/installd/dexopt.cpp
+++ b/cmds/installd/dexopt.cpp
@@ -292,8 +292,8 @@
     }
 }
 
-static unique_fd create_profile(uid_t uid, const std::string& profile, int32_t flags) {
-    unique_fd fd(TEMP_FAILURE_RETRY(open(profile.c_str(), flags, 0600)));
+static unique_fd create_profile(uid_t uid, const std::string& profile, int32_t flags, mode_t mode) {
+    unique_fd fd(TEMP_FAILURE_RETRY(open(profile.c_str(), flags, mode)));
     if (fd.get() < 0) {
         if (errno != EEXIST) {
             PLOG(ERROR) << "Failed to create profile " << profile;
@@ -310,7 +310,7 @@
     return fd;
 }
 
-static unique_fd open_profile(uid_t uid, const std::string& profile, int32_t flags) {
+static unique_fd open_profile(uid_t uid, const std::string& profile, int32_t flags, mode_t mode) {
     // Do not follow symlinks when opening a profile:
     //   - primary profiles should not contain symlinks in their paths
     //   - secondary dex paths should have been already resolved and validated
@@ -320,7 +320,7 @@
     // Reference profiles and snapshots are created on the fly; so they might not exist beforehand.
     unique_fd fd;
     if ((flags & O_CREAT) != 0) {
-        fd = create_profile(uid, profile, flags);
+        fd = create_profile(uid, profile, flags, mode);
     } else {
         fd.reset(TEMP_FAILURE_RETRY(open(profile.c_str(), flags)));
     }
@@ -336,6 +336,16 @@
             PLOG(ERROR) << "Failed to open profile " << profile;
         }
         return invalid_unique_fd();
+    } else {
+        // If we just create the file we need to set its mode because on Android
+        // open has a mask that only allows owner access.
+        if ((flags & O_CREAT) != 0) {
+            if (fchmod(fd.get(), mode) != 0) {
+                PLOG(ERROR) << "Could not set mode " << std::hex << mode << std::dec
+                        << " on profile" << profile;
+                // Not a terminal failure.
+            }
+        }
     }
 
     return fd;
@@ -345,20 +355,29 @@
         const std::string& location, bool is_secondary_dex) {
     std::string profile = create_current_profile_path(user, package_name, location,
             is_secondary_dex);
-    return open_profile(uid, profile, O_RDONLY);
+    return open_profile(uid, profile, O_RDONLY, /*mode=*/ 0);
 }
 
 static unique_fd open_reference_profile(uid_t uid, const std::string& package_name,
         const std::string& location, bool read_write, bool is_secondary_dex) {
     std::string profile = create_reference_profile_path(package_name, location, is_secondary_dex);
-    return open_profile(uid, profile, read_write ? (O_CREAT | O_RDWR) : O_RDONLY);
+    return open_profile(
+        uid,
+        profile,
+        read_write ? (O_CREAT | O_RDWR) : O_RDONLY,
+        S_IRUSR | S_IWUSR | S_IRGRP);  // so that ART can also read it when apps run.
 }
 
 static UniqueFile open_reference_profile_as_unique_file(uid_t uid, const std::string& package_name,
         const std::string& location, bool read_write, bool is_secondary_dex) {
     std::string profile_path = create_reference_profile_path(package_name, location,
                                                              is_secondary_dex);
-    unique_fd ufd = open_profile(uid, profile_path, read_write ? (O_CREAT | O_RDWR) : O_RDONLY);
+    unique_fd ufd = open_profile(
+        uid,
+        profile_path,
+        read_write ? (O_CREAT | O_RDWR) : O_RDONLY,
+        S_IRUSR | S_IWUSR | S_IRGRP);  // so that ART can also read it when apps run.
+
     return UniqueFile(ufd.release(), profile_path, [](const std::string& path) {
         clear_profile(path);
     });
@@ -367,7 +386,7 @@
 static unique_fd open_spnashot_profile(uid_t uid, const std::string& package_name,
         const std::string& location) {
     std::string profile = create_snapshot_profile_path(package_name, location);
-    return open_profile(uid, profile, O_CREAT | O_RDWR | O_TRUNC);
+    return open_profile(uid, profile, O_CREAT | O_RDWR | O_TRUNC,  S_IRUSR | S_IWUSR);
 }
 
 static void open_profile_files(uid_t uid, const std::string& package_name,
@@ -2484,7 +2503,7 @@
     for (size_t i = 0; i < profiles.size(); )  {
         std::vector<unique_fd> profiles_fd;
         for (size_t k = 0; k < kAggregationBatchSize && i < profiles.size(); k++, i++) {
-            unique_fd fd = open_profile(AID_SYSTEM, profiles[i], O_RDONLY);
+            unique_fd fd = open_profile(AID_SYSTEM, profiles[i], O_RDONLY, /*mode=*/ 0);
             if (fd.get() >= 0) {
                 profiles_fd.push_back(std::move(fd));
             }
diff --git a/cmds/installd/tests/installd_dexopt_test.cpp b/cmds/installd/tests/installd_dexopt_test.cpp
index e272025..216347e 100644
--- a/cmds/installd/tests/installd_dexopt_test.cpp
+++ b/cmds/installd/tests/installd_dexopt_test.cpp
@@ -919,7 +919,7 @@
             return;
         }
 
-        // Check that the snapshot was created witht he expected acess flags.
+        // Check that the snapshot was created with the expected access flags.
         CheckFileAccess(snap_profile_, kSystemUid, kSystemGid, 0600 | S_IFREG);
 
         // The snapshot should be equivalent to the merge of profiles.
@@ -962,8 +962,8 @@
             return;
         }
 
-        // Check that the snapshot was created witht he expected acess flags.
-        CheckFileAccess(ref_profile_, kTestAppUid, kTestAppUid, 0600 | S_IFREG);
+        // Check that the snapshot was created with the expected access flags.
+        CheckFileAccess(ref_profile_, kTestAppUid, kTestAppUid, 0640 | S_IFREG);
 
         // The snapshot should be equivalent to the merge of profiles.
         std::string ref_profile_content = ref_profile_ + ".expected";
diff --git a/libs/permission/android/permission/PermissionChecker.cpp b/libs/permission/android/permission/PermissionChecker.cpp
index 008afad..66526f9 100644
--- a/libs/permission/android/permission/PermissionChecker.cpp
+++ b/libs/permission/android/permission/PermissionChecker.cpp
@@ -82,6 +82,14 @@
             /*startDataDelivery*/ true, /*fromDatasource*/ true, attributedOpCode);
 }
 
+PermissionChecker::PermissionResult PermissionChecker::checkPermissionForPreflight(
+        const String16& permission, const AttributionSourceState& attributionSource,
+        const String16& message, int32_t attributedOpCode)
+{
+    return checkPermission(permission, attributionSource, message, /*forDataDelivery*/ false,
+            /*startDataDelivery*/ false, /*fromDatasource*/ false, attributedOpCode);
+}
+
 PermissionChecker::PermissionResult PermissionChecker::checkPermissionForPreflightFromDatasource(
         const String16& permission, const AttributionSourceState& attributionSource,
         const String16& message, int32_t attributedOpCode)
diff --git a/libs/permission/include/android/permission/PermissionChecker.h b/libs/permission/include/android/permission/PermissionChecker.h
index 308d794..21515e3 100644
--- a/libs/permission/include/android/permission/PermissionChecker.h
+++ b/libs/permission/include/android/permission/PermissionChecker.h
@@ -101,6 +101,29 @@
      * has a given permission and whether the app op that corresponds to this permission
      * is allowed. The app ops are not noted/started.
      *
+     * NOTE: Use this method only for permission checks at the preflight point where you
+     * will not deliver the permission protected data to clients but schedule permission
+     * data delivery, apps register listeners, etc.
+     *
+     * @param permission The permission to check.
+     * @param attributionSource The attribution chain to check.
+     * @param message A message describing the reason the permission was checked.
+     * @param attributedOpCode The op code towards which to blame the access. If this
+     *     is a valid app op the op corresponding to the checked permission (if such)
+     *     would only be checked to ensure it is allowed and if that succeeds the
+     *     starting would be against the attributed op.
+     * @return The permission check result which is either PERMISSION_GRANTED,
+     *     or PERMISSION_SOFT_DENIED or PERMISSION_HARD_DENIED.
+     */
+    PermissionResult checkPermissionForPreflight(
+            const String16& permission, const AttributionSourceState& attributionSource,
+            const String16& message, int32_t attributedOpCode);
+
+   /**
+     * Checks whether a given data access chain described by the given attribution source
+     * has a given permission and whether the app op that corresponds to this permission
+     * is allowed. The app ops are not noted/started.
+     *
      * NOTE: The attribution source should be for yourself with its next attribution
      * source being the app that would receive the data from you.
      *
diff --git a/libs/renderengine/skia/Cache.cpp b/libs/renderengine/skia/Cache.cpp
index 01c84a9..b3975b0 100644
--- a/libs/renderengine/skia/Cache.cpp
+++ b/libs/renderengine/skia/Cache.cpp
@@ -149,6 +149,7 @@
                     PixelSource{
                             .solidColor = half3(0.1f, 0.2f, 0.3f),
                     },
+            .alpha = 0.5,
     };
 
     auto layers = std::vector<const LayerSettings*>{&layer};
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
index 2d80c46..9857fc2 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
@@ -36,6 +36,7 @@
 #include <SkSurface.h>
 #include <android-base/stringprintf.h>
 #include <gl/GrGLInterface.h>
+#include <gui/TraceUtils.h>
 #include <sync/sync.h>
 #include <ui/BlurRegion.h>
 #include <ui/DebugUtils.h>
@@ -315,6 +316,7 @@
     GrContextOptions options;
     options.fDisableDriverCorrectnessWorkarounds = true;
     options.fDisableDistanceFieldPaths = true;
+    options.fReducedShaderVariations = true;
     options.fPersistentCache = &mSkSLCacheMonitor;
     mGrContext = GrDirectContext::MakeGL(glInterface, options);
     if (useProtectedContext(true)) {
@@ -506,17 +508,18 @@
         return;
     }
     // We currently don't attempt to map a buffer if the buffer contains protected content
-    // or we are using a protected context because GPU resources for protected buffers is
-    // much more limited.
+    // because GPU resources for protected buffers is much more limited.
     const bool isProtectedBuffer = buffer->getUsage() & GRALLOC_USAGE_PROTECTED;
-    if (isProtectedBuffer || mInProtectedContext) {
+    if (isProtectedBuffer) {
         return;
     }
     ATRACE_CALL();
 
-    // If we were to support caching protected buffers then we will need to switch the currently
-    // bound context if we are not already using the protected context (and subsequently switch
-    // back after the buffer is cached).
+    // If we were to support caching protected buffers then we will need to switch the
+    // currently bound context if we are not already using the protected context (and subsequently
+    // switch back after the buffer is cached).  However, for non-protected content we can bind
+    // the texture in either GL context because they are initialized with the same share_context
+    // which allows the texture state to be shared between them.
     auto grContext = getActiveGrContext();
     auto& cache = mTextureCache;
 
@@ -550,7 +553,6 @@
 
         if (iter->second == 0) {
             mTextureCache.erase(buffer->getId());
-            mProtectedTextureCache.erase(buffer->getId());
             mGraphicBufferExternalRefs.erase(buffer->getId());
         }
     }
@@ -702,7 +704,7 @@
     validateOutputBufferUsage(buffer->getBuffer());
 
     auto grContext = getActiveGrContext();
-    auto& cache = mInProtectedContext ? mProtectedTextureCache : mTextureCache;
+    auto& cache = mTextureCache;
 
     std::shared_ptr<AutoBackendTexture::LocalRef> surfaceTextureRef;
     if (const auto& it = cache.find(buffer->getBuffer()->getId()); it != cache.end()) {
@@ -792,7 +794,7 @@
     }
 
     for (const auto& layer : layers) {
-        ATRACE_NAME("DrawLayer");
+        ATRACE_FORMAT("DrawLayer: %s", layer->name.c_str());
 
         if (kPrintLayerSettings) {
             std::stringstream ls;
@@ -1445,12 +1447,6 @@
         StringAppendF(&result, "Skia's Protected Wrapped Objects:\n");
         gpuProtectedReporter.logOutput(result, true);
 
-        StringAppendF(&result, "RenderEngine protected AHB/BackendTexture cache size: %zu\n",
-                      mProtectedTextureCache.size());
-        StringAppendF(&result, "Dumping buffer ids...\n");
-        for (const auto& [id, unused] : mProtectedTextureCache) {
-            StringAppendF(&result, "- 0x%" PRIx64 "\n", id);
-        }
         StringAppendF(&result, "\n");
         StringAppendF(&result, "RenderEngine runtime effects: %zu\n", mRuntimeEffects.size());
         for (const auto& [linearEffect, unused] : mRuntimeEffects) {
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.h b/libs/renderengine/skia/SkiaGLRenderEngine.h
index 97d3b72..758c0f8 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.h
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.h
@@ -126,11 +126,9 @@
     // Number of external holders of ExternalTexture references, per GraphicBuffer ID.
     std::unordered_map<GraphicBufferId, int32_t> mGraphicBufferExternalRefs
             GUARDED_BY(mRenderingMutex);
-    // Cache of GL textures that we'll store per GraphicBuffer ID, sliced by GPU context.
+    // Cache of GL textures that we'll store per GraphicBuffer ID, shared between GPU contexts.
     std::unordered_map<GraphicBufferId, std::shared_ptr<AutoBackendTexture::LocalRef>> mTextureCache
             GUARDED_BY(mRenderingMutex);
-    std::unordered_map<GraphicBufferId, std::shared_ptr<AutoBackendTexture::LocalRef>>
-            mProtectedTextureCache GUARDED_BY(mRenderingMutex);
     std::unordered_map<LinearEffect, sk_sp<SkRuntimeEffect>, LinearEffectHasher> mRuntimeEffects;
 
     StretchShaderFactory mStretchShaderFactory;
diff --git a/libs/renderengine/threaded/RenderEngineThreaded.cpp b/libs/renderengine/threaded/RenderEngineThreaded.cpp
index 9009ce4..5febd8c 100644
--- a/libs/renderengine/threaded/RenderEngineThreaded.cpp
+++ b/libs/renderengine/threaded/RenderEngineThreaded.cpp
@@ -48,11 +48,8 @@
 }
 
 RenderEngineThreaded::~RenderEngineThreaded() {
-    {
-        std::lock_guard lock(mThreadMutex);
-        mRunning = false;
-        mCondition.notify_one();
-    }
+    mRunning = false;
+    mCondition.notify_one();
 
     if (mThread.joinable()) {
         mThread.join();
@@ -71,21 +68,32 @@
 
     mRenderEngine = factory();
 
-    std::unique_lock<std::mutex> lock(mThreadMutex);
     pthread_setname_np(pthread_self(), mThreadName);
 
     {
-        std::unique_lock<std::mutex> lock(mInitializedMutex);
+        std::scoped_lock lock(mInitializedMutex);
         mIsInitialized = true;
     }
     mInitializedCondition.notify_all();
 
     while (mRunning) {
-        if (!mFunctionCalls.empty()) {
-            auto task = mFunctionCalls.front();
-            mFunctionCalls.pop();
-            task(*mRenderEngine);
+        const auto getNextTask = [this]() -> std::optional<Work> {
+            std::scoped_lock lock(mThreadMutex);
+            if (!mFunctionCalls.empty()) {
+                Work task = mFunctionCalls.front();
+                mFunctionCalls.pop();
+                return std::make_optional<Work>(task);
+            }
+            return std::nullopt;
+        };
+
+        const auto task = getNextTask();
+
+        if (task) {
+            (*task)(*mRenderEngine);
         }
+
+        std::unique_lock<std::mutex> lock(mThreadMutex);
         mCondition.wait(lock, [this]() REQUIRES(mThreadMutex) {
             return !mRunning || !mFunctionCalls.empty();
         });
diff --git a/libs/renderengine/threaded/RenderEngineThreaded.h b/libs/renderengine/threaded/RenderEngineThreaded.h
index eb6098e..b78bf21 100644
--- a/libs/renderengine/threaded/RenderEngineThreaded.h
+++ b/libs/renderengine/threaded/RenderEngineThreaded.h
@@ -82,9 +82,10 @@
     // Protects the creation and destruction of mThread.
     mutable std::mutex mThreadMutex;
     std::thread mThread GUARDED_BY(mThreadMutex);
-    bool mRunning GUARDED_BY(mThreadMutex) = true;
-    mutable std::queue<std::function<void(renderengine::RenderEngine& instance)>> mFunctionCalls
-            GUARDED_BY(mThreadMutex);
+    std::atomic<bool> mRunning = true;
+
+    using Work = std::function<void(renderengine::RenderEngine&)>;
+    mutable std::queue<Work> mFunctionCalls GUARDED_BY(mThreadMutex);
     mutable std::condition_variable mCondition;
 
     // Used to allow select thread safe methods to be accessed without requiring the
diff --git a/services/sensorservice/SensorService.cpp b/services/sensorservice/SensorService.cpp
index 9df020d..a1c800e 100644
--- a/services/sensorservice/SensorService.cpp
+++ b/services/sensorservice/SensorService.cpp
@@ -2172,6 +2172,13 @@
     return true;
 }
 
+/**
+ * Checks if a sensor should be capped according to HIGH_SAMPLING_RATE_SENSORS
+ * permission.
+ *
+ * This needs to be kept in sync with the list defined on the Java side
+ * in frameworks/base/core/java/android/hardware/SystemSensorManager.java
+ */
 bool SensorService::isSensorInCappedSet(int sensorType) {
     return (sensorType == SENSOR_TYPE_ACCELEROMETER
             || sensorType == SENSOR_TYPE_ACCELEROMETER_UNCALIBRATED
diff --git a/services/surfaceflinger/CompositionEngine/Android.bp b/services/surfaceflinger/CompositionEngine/Android.bp
index 08147ed..d738ccd 100644
--- a/services/surfaceflinger/CompositionEngine/Android.bp
+++ b/services/surfaceflinger/CompositionEngine/Android.bp
@@ -57,6 +57,7 @@
         "src/planner/LayerState.cpp",
         "src/planner/Planner.cpp",
         "src/planner/Predictor.cpp",
+        "src/planner/TexturePool.cpp",
         "src/ClientCompositionRequestCache.cpp",
         "src/CompositionEngine.cpp",
         "src/Display.cpp",
@@ -107,6 +108,7 @@
         "tests/planner/FlattenerTest.cpp",
         "tests/planner/LayerStateTest.cpp",
         "tests/planner/PredictorTest.cpp",
+        "tests/planner/TexturePoolTest.cpp",
         "tests/CompositionEngineTest.cpp",
         "tests/DisplayColorProfileTest.cpp",
         "tests/DisplayTest.cpp",
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h
index 2ffd472..244f8ab 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h
@@ -73,10 +73,11 @@
     void writeOutputIndependentGeometryStateToHWC(HWC2::Layer*, const LayerFECompositionState&,
                                                   bool skipLayer);
     void writeOutputDependentPerFrameStateToHWC(HWC2::Layer*);
-    void writeOutputIndependentPerFrameStateToHWC(HWC2::Layer*, const LayerFECompositionState&);
+    void writeOutputIndependentPerFrameStateToHWC(HWC2::Layer*, const LayerFECompositionState&,
+                                                  bool skipLayer);
     void writeSolidColorStateToHWC(HWC2::Layer*, const LayerFECompositionState&);
     void writeSidebandStateToHWC(HWC2::Layer*, const LayerFECompositionState&);
-    void writeBufferStateToHWC(HWC2::Layer*, const LayerFECompositionState&);
+    void writeBufferStateToHWC(HWC2::Layer*, const LayerFECompositionState&, bool skipLayer);
     void writeCompositionTypeToHWC(HWC2::Layer*, Hwc2::IComposerClient::Composition,
                                    bool isPeekingThrough, bool skipLayer);
     void detectDisallowedCompositionTypeChange(Hwc2::IComposerClient::Composition from,
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h
index 2c18a60..a4356c5 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h
@@ -19,6 +19,7 @@
 #include <compositionengine/Output.h>
 #include <compositionengine/ProjectionSpace.h>
 #include <compositionengine/impl/planner/LayerState.h>
+#include <compositionengine/impl/planner/TexturePool.h>
 #include <renderengine/RenderEngine.h>
 
 #include <chrono>
@@ -64,9 +65,12 @@
     size_t getLayerCount() const { return mLayers.size(); }
     const Layer& getFirstLayer() const { return mLayers[0]; }
     const Rect& getBounds() const { return mBounds; }
+    Rect getTextureBounds() const { return mOutputSpace.content; }
     const Region& getVisibleRegion() const { return mVisibleRegion; }
     size_t getAge() const { return mAge; }
-    const std::shared_ptr<renderengine::ExternalTexture>& getBuffer() const { return mTexture; }
+    std::shared_ptr<renderengine::ExternalTexture> getBuffer() const {
+        return mTexture ? mTexture->get() : nullptr;
+    }
     const sp<Fence>& getDrawFence() const { return mDrawFence; }
     const ProjectionSpace& getOutputSpace() const { return mOutputSpace; }
     ui::Dataspace getOutputDataspace() const { return mOutputDataspace; }
@@ -89,7 +93,7 @@
 
     void setLastUpdate(std::chrono::steady_clock::time_point now) { mLastUpdate = now; }
     void append(const CachedSet& other) {
-        mTexture = nullptr;
+        mTexture.reset();
         mOutputDataspace = ui::Dataspace::UNKNOWN;
         mDrawFence = nullptr;
         mBlurLayer = nullptr;
@@ -105,7 +109,8 @@
     void incrementAge() { ++mAge; }
 
     // Renders the cached set with the supplied output composition state.
-    void render(renderengine::RenderEngine& re, const OutputCompositionState& outputState);
+    void render(renderengine::RenderEngine& re, TexturePool& texturePool,
+                const OutputCompositionState& outputState);
 
     void dump(std::string& result) const;
 
@@ -151,7 +156,9 @@
     Region mVisibleRegion;
     size_t mAge = 0;
 
-    std::shared_ptr<renderengine::ExternalTexture> mTexture;
+    // TODO(b/190411067): This is a shared pointer only because CachedSets are copied into different
+    // containers in the Flattener. Logically this should have unique ownership otherwise.
+    std::shared_ptr<TexturePool::AutoTexture> mTexture;
     sp<Fence> mDrawFence;
     ProjectionSpace mOutputSpace;
     ui::Dataspace mOutputDataspace;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h
index ca1d69d..94a169e 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h
@@ -37,16 +37,18 @@
 
 class Flattener {
 public:
-    Flattener(bool enableHolePunch = false) : mEnableHolePunch(enableHolePunch) {}
+    Flattener(renderengine::RenderEngine& renderEngine, bool enableHolePunch = false);
 
-    void setDisplaySize(ui::Size size) { mDisplaySize = size; }
+    void setDisplaySize(ui::Size size) {
+        mDisplaySize = size;
+        mTexturePool.setDisplaySize(size);
+    }
 
     NonBufferHash flattenLayers(const std::vector<const LayerState*>& layers, NonBufferHash,
                                 std::chrono::steady_clock::time_point now);
 
     // Renders the newest cached sets with the supplied output composition state
-    void renderCachedSets(renderengine::RenderEngine& re,
-                          const OutputCompositionState& outputState);
+    void renderCachedSets(const OutputCompositionState& outputState);
 
     void dump(std::string& result) const;
     void dumpLayers(std::string& result) const;
@@ -145,8 +147,11 @@
 
     void buildCachedSets(std::chrono::steady_clock::time_point now);
 
+    renderengine::RenderEngine& mRenderEngine;
     const bool mEnableHolePunch;
 
+    TexturePool mTexturePool;
+
     ui::Size mDisplaySize;
 
     NonBufferHash mCurrentGeometry;
@@ -162,6 +167,7 @@
     size_t mCachedSetCreationCount = 0;
     size_t mCachedSetCreationCost = 0;
     std::unordered_map<size_t, size_t> mInvalidatedCachedSetAges;
+    std::chrono::nanoseconds mActiveLayerTimeout = kActiveLayerTimeout;
 
     static constexpr auto kActiveLayerTimeout = std::chrono::nanoseconds(150ms);
 };
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Planner.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Planner.h
index 4365b93..fd1ddfc 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Planner.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Planner.h
@@ -41,7 +41,7 @@
 // as a more efficient representation of parts of the layer stack.
 class Planner {
 public:
-    Planner();
+    Planner(renderengine::RenderEngine& renderengine);
 
     void setDisplaySize(ui::Size);
 
@@ -59,8 +59,7 @@
             compositionengine::Output::OutputLayersEnumerator<compositionengine::Output>&& layers);
 
     // The planner will call to the Flattener to render any pending cached set
-    void renderCachedSets(renderengine::RenderEngine& re,
-                          const OutputCompositionState& outputState);
+    void renderCachedSets(const OutputCompositionState& outputState);
 
     void dump(const Vector<String16>& args, std::string&);
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/TexturePool.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/TexturePool.h
new file mode 100644
index 0000000..fb53ee0
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/TexturePool.h
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <compositionengine/Output.h>
+#include <compositionengine/ProjectionSpace.h>
+#include <compositionengine/impl/planner/LayerState.h>
+#include <renderengine/RenderEngine.h>
+
+#include <renderengine/ExternalTexture.h>
+#include <chrono>
+#include "android-base/macros.h"
+
+namespace android::compositionengine::impl::planner {
+
+// A pool of textures that only manages textures of a single size.
+// While it is possible to define a texture pool supporting variable-sized textures to save on
+// memory, it is a simpler implementation to only manage screen-sized textures. The texture pool is
+// unbounded - there are a minimum number of textures preallocated. Under heavy system load, new
+// textures may be allocated, but only a maximum number of retained once those textures are no
+// longer necessary.
+class TexturePool {
+public:
+    // RAII class helping with managing textures from the texture pool
+    // Textures once they're no longer used should be returned to the pool instead of outright
+    // deleted.
+    class AutoTexture {
+    public:
+        AutoTexture(TexturePool& texturePool,
+                    std::shared_ptr<renderengine::ExternalTexture> texture, const sp<Fence>& fence)
+              : mTexturePool(texturePool), mTexture(texture), mFence(fence) {}
+
+        ~AutoTexture() { mTexturePool.returnTexture(std::move(mTexture), mFence); }
+
+        sp<Fence> getReadyFence() { return mFence; }
+
+        void setReadyFence(const sp<Fence>& fence) { mFence = fence; }
+
+        // Disable copying and assigning
+        AutoTexture(const AutoTexture&) = delete;
+        AutoTexture& operator=(const AutoTexture&) = delete;
+
+        // Gets a pointer to the underlying external texture
+        const std::shared_ptr<renderengine::ExternalTexture>& get() const { return mTexture; }
+
+    private:
+        TexturePool& mTexturePool;
+        std::shared_ptr<renderengine::ExternalTexture> mTexture;
+        sp<Fence> mFence;
+    };
+
+    TexturePool(renderengine::RenderEngine& renderEngine) : mRenderEngine(renderEngine) {}
+
+    virtual ~TexturePool() = default;
+
+    // Sets the display size for the texture pool.
+    // This will trigger a reallocation for all remaining textures in the pool.
+    // setDisplaySize must be called for the texture pool to be used.
+    void setDisplaySize(ui::Size size);
+
+    // Borrows a new texture from the pool.
+    // If the pool is currently starved of textures, then a new texture is generated.
+    // When the AutoTexture object is destroyed, the scratch texture is automatically returned
+    // to the pool.
+    std::shared_ptr<AutoTexture> borrowTexture();
+
+protected:
+    // Proteted visibility so that they can be used for testing
+    const static constexpr size_t kMinPoolSize = 3;
+    const static constexpr size_t kMaxPoolSize = 4;
+
+    struct Entry {
+        std::shared_ptr<renderengine::ExternalTexture> texture;
+        sp<Fence> fence;
+    };
+
+    std::deque<Entry> mPool;
+
+private:
+    std::shared_ptr<renderengine::ExternalTexture> genTexture();
+    // Returns a previously borrowed texture to the pool.
+    void returnTexture(std::shared_ptr<renderengine::ExternalTexture>&& texture,
+                       const sp<Fence>& fence);
+    renderengine::RenderEngine& mRenderEngine;
+    ui::Size mSize;
+};
+
+} // namespace android::compositionengine::impl::planner
diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp
index e9a8b91..cd2f742 100644
--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -129,7 +129,7 @@
     }
 
     if (enabled) {
-        mPlanner = std::make_unique<planner::Planner>();
+        mPlanner = std::make_unique<planner::Planner>(getCompositionEngine().getRenderEngine());
         if (mRenderSurface) {
             mPlanner->setDisplaySize(mRenderSurface->getSize());
         }
@@ -1314,7 +1314,7 @@
 
 void Output::renderCachedSets() {
     if (mPlanner) {
-        mPlanner->renderCachedSets(getCompositionEngine().getRenderEngine(), getState());
+        mPlanner->renderCachedSets(getState());
     }
 }
 
diff --git a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
index cd14327..e4e46a7 100644
--- a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
@@ -349,7 +349,7 @@
     }
 
     writeOutputDependentPerFrameStateToHWC(hwcLayer.get());
-    writeOutputIndependentPerFrameStateToHWC(hwcLayer.get(), *outputIndependentState);
+    writeOutputIndependentPerFrameStateToHWC(hwcLayer.get(), *outputIndependentState, skipLayer);
 
     writeCompositionTypeToHWC(hwcLayer.get(), requestedCompositionType, isPeekingThrough,
                               skipLayer);
@@ -471,7 +471,8 @@
 }
 
 void OutputLayer::writeOutputIndependentPerFrameStateToHWC(
-        HWC2::Layer* hwcLayer, const LayerFECompositionState& outputIndependentState) {
+        HWC2::Layer* hwcLayer, const LayerFECompositionState& outputIndependentState,
+        bool skipLayer) {
     switch (auto error = hwcLayer->setColorTransform(outputIndependentState.colorTransform)) {
         case hal::Error::NONE:
             break;
@@ -504,7 +505,7 @@
             break;
         case hal::Composition::CURSOR:
         case hal::Composition::DEVICE:
-            writeBufferStateToHWC(hwcLayer, outputIndependentState);
+            writeBufferStateToHWC(hwcLayer, outputIndependentState, skipLayer);
             break;
         case hal::Composition::INVALID:
         case hal::Composition::CLIENT:
@@ -541,7 +542,8 @@
 }
 
 void OutputLayer::writeBufferStateToHWC(HWC2::Layer* hwcLayer,
-                                        const LayerFECompositionState& outputIndependentState) {
+                                        const LayerFECompositionState& outputIndependentState,
+                                        bool skipLayer) {
     auto supportedPerFrameMetadata =
             getOutput().getDisplayColorProfile()->getSupportedPerFrameMetadata();
     if (auto error = hwcLayer->setPerFrameMetadata(supportedPerFrameMetadata,
@@ -554,7 +556,7 @@
     sp<GraphicBuffer> buffer = outputIndependentState.buffer;
     sp<Fence> acquireFence = outputIndependentState.acquireFence;
     int slot = outputIndependentState.bufferSlot;
-    if (getState().overrideInfo.buffer != nullptr) {
+    if (getState().overrideInfo.buffer != nullptr && !skipLayer) {
         buffer = getState().overrideInfo.buffer->getBuffer();
         acquireFence = getState().overrideInfo.acquireFence;
         slot = HwcBufferCache::FLATTENER_CACHING_SLOT;
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
index dcfb05d..68b6231 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
@@ -134,7 +134,7 @@
 }
 
 bool CachedSet::hasReadyBuffer() const {
-    return mTexture != nullptr && mDrawFence->getStatus() == Fence::Status::Signaled;
+    return mTexture && mDrawFence->getStatus() == Fence::Status::Signaled;
 }
 
 std::vector<CachedSet> CachedSet::decompose() const {
@@ -156,7 +156,7 @@
     }
 }
 
-void CachedSet::render(renderengine::RenderEngine& renderEngine,
+void CachedSet::render(renderengine::RenderEngine& renderEngine, TexturePool& texturePool,
                        const OutputCompositionState& outputState) {
     ATRACE_CALL();
     const Rect& viewport = outputState.layerStackSpace.content;
@@ -165,10 +165,7 @@
             ui::Transform::toRotationFlags(outputState.framebufferSpace.orientation);
 
     renderengine::DisplaySettings displaySettings{
-            .physicalDisplay = Rect(-mBounds.left + outputState.framebufferSpace.content.left,
-                                    -mBounds.top + outputState.framebufferSpace.content.top,
-                                    -mBounds.left + outputState.framebufferSpace.content.right,
-                                    -mBounds.top + outputState.framebufferSpace.content.bottom),
+            .physicalDisplay = outputState.framebufferSpace.content,
             .clip = viewport,
             .outputDataspace = outputDataspace,
             .orientation = orientation,
@@ -255,30 +252,33 @@
         layerSettingsPointers.emplace_back(&highlight);
     }
 
-    const uint64_t usageFlags = GraphicBuffer::USAGE_HW_RENDER | GraphicBuffer::USAGE_HW_COMPOSER |
-            GraphicBuffer::USAGE_HW_TEXTURE;
-    sp<GraphicBuffer> buffer = new GraphicBuffer(static_cast<uint32_t>(mBounds.getWidth()),
-                                                 static_cast<uint32_t>(mBounds.getHeight()),
-                                                 HAL_PIXEL_FORMAT_RGBA_8888, 1, usageFlags);
-    const auto texture = std::make_shared<
-            renderengine::ExternalTexture>(buffer, renderEngine,
-                                           renderengine::ExternalTexture::Usage::READABLE |
-                                                   renderengine::ExternalTexture::Usage::WRITEABLE);
-    LOG_ALWAYS_FATAL_IF(buffer->initCheck() != OK);
-    base::unique_fd drawFence;
+    auto texture = texturePool.borrowTexture();
+    LOG_ALWAYS_FATAL_IF(texture->get()->getBuffer()->initCheck() != OK);
 
-    status_t result = renderEngine.drawLayers(displaySettings, layerSettingsPointers, texture,
-                                              false, base::unique_fd(), &drawFence);
+    base::unique_fd bufferFence;
+    if (texture->getReadyFence()) {
+        // Bail out if the buffer is not ready, because there is some pending GPU work left.
+        if (texture->getReadyFence()->getStatus() != Fence::Status::Signaled) {
+            return;
+        }
+        bufferFence.reset(texture->getReadyFence()->dup());
+    }
+
+    base::unique_fd drawFence;
+    status_t result =
+            renderEngine.drawLayers(displaySettings, layerSettingsPointers, texture->get(), false,
+                                    std::move(bufferFence), &drawFence);
 
     if (result == NO_ERROR) {
         mDrawFence = new Fence(drawFence.release());
         mOutputSpace = outputState.framebufferSpace;
-        mTexture = std::move(texture);
+        mTexture = texture;
+        mTexture->setReadyFence(mDrawFence);
         mOutputSpace.orientation = outputState.framebufferSpace.orientation;
         mOutputDataspace = outputDataspace;
         mOrientation = orientation;
     } else {
-        mTexture = nullptr;
+        mTexture.reset();
     }
 }
 
@@ -294,6 +294,12 @@
         return false;
     }
 
+    // Do not use a hole punch with an HDR layer; this should be done in client
+    // composition to properly mix HDR with SDR.
+    if (hasHdrLayers()) {
+        return false;
+    }
+
     const auto& layerFE = mLayers[0].getState()->getOutputLayer()->getLayerFE();
     if (layerFE.getCompositionState()->forceClientComposition) {
         return false;
@@ -363,7 +369,7 @@
     base::StringAppendF(&result, "  + Fingerprint %016zx, last update %sago, age %zd\n",
                         mFingerprint, durationString(lastUpdate).c_str(), mAge);
     {
-        const auto b = mTexture ? mTexture->getBuffer().get() : nullptr;
+        const auto b = mTexture ? mTexture->get()->getBuffer().get() : nullptr;
         base::StringAppendF(&result, "    Override buffer: %p\n", b);
     }
     base::StringAppendF(&result, "    HolePunchLayer: %p\n", mHolePunchLayer);
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
index 233b5f2..192c411 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
@@ -19,6 +19,7 @@
 // #define LOG_NDEBUG 0
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
+#include <android-base/properties.h>
 #include <compositionengine/impl/planner/Flattener.h>
 #include <compositionengine/impl/planner/LayerState.h>
 
@@ -59,6 +60,17 @@
 
 } // namespace
 
+Flattener::Flattener(renderengine::RenderEngine& renderEngine, bool enableHolePunch)
+      : mRenderEngine(renderEngine),
+        mEnableHolePunch(enableHolePunch),
+        mTexturePool(mRenderEngine) {
+    const int timeoutInMs =
+            base::GetIntProperty(std::string("debug.sf.layer_caching_active_layer_timeout_ms"), 0);
+    if (timeoutInMs != 0) {
+        mActiveLayerTimeout = std::chrono::milliseconds(timeoutInMs);
+    }
+}
+
 NonBufferHash Flattener::flattenLayers(const std::vector<const LayerState*>& layers,
                                        NonBufferHash hash, time_point now) {
     ATRACE_CALL();
@@ -93,14 +105,13 @@
     return hash;
 }
 
-void Flattener::renderCachedSets(renderengine::RenderEngine& renderEngine,
-                                 const OutputCompositionState& outputState) {
+void Flattener::renderCachedSets(const OutputCompositionState& outputState) {
     ATRACE_CALL();
     if (!mNewCachedSet || mNewCachedSet->hasRenderedBuffer()) {
         return;
     }
 
-    mNewCachedSet->render(renderEngine, outputState);
+    mNewCachedSet->render(mRenderEngine, mTexturePool, outputState);
 }
 
 void Flattener::dumpLayers(std::string& result) const {
@@ -276,7 +287,7 @@
                         state.overrideInfo = {
                                 .buffer = mNewCachedSet->getBuffer(),
                                 .acquireFence = mNewCachedSet->getDrawFence(),
-                                .displayFrame = mNewCachedSet->getBounds(),
+                                .displayFrame = mNewCachedSet->getTextureBounds(),
                                 .dataspace = mNewCachedSet->getOutputDataspace(),
                                 .displaySpace = mNewCachedSet->getOutputSpace(),
                                 .damageRegion = Region::INVALID_REGION,
@@ -316,7 +327,7 @@
                 state.overrideInfo = {
                         .buffer = currentLayerIter->getBuffer(),
                         .acquireFence = currentLayerIter->getDrawFence(),
-                        .displayFrame = currentLayerIter->getBounds(),
+                        .displayFrame = currentLayerIter->getTextureBounds(),
                         .dataspace = currentLayerIter->getOutputDataspace(),
                         .displaySpace = currentLayerIter->getOutputSpace(),
                         .damageRegion = Region(),
@@ -370,7 +381,7 @@
     bool runHasFirstLayer = false;
 
     for (auto currentSet = mLayers.cbegin(); currentSet != mLayers.cend(); ++currentSet) {
-        const bool layerIsInactive = now - currentSet->getLastUpdate() > kActiveLayerTimeout;
+        const bool layerIsInactive = now - currentSet->getLastUpdate() > mActiveLayerTimeout;
         const bool layerHasBlur = currentSet->hasBlurBehind();
         if (layerIsInactive && (firstLayer || runHasFirstLayer || !layerHasBlur) &&
             !currentSet->hasHdrLayers() && !currentSet->hasProtectedLayers()) {
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp
index 297c0b2..711a634 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp
@@ -29,12 +29,13 @@
 
 namespace android::compositionengine::impl::planner {
 
-Planner::Planner()
+Planner::Planner(renderengine::RenderEngine& renderEngine)
       // Implicitly, layer caching must also be enabled for the hole punch or
       // predictor to have any effect.
       // E.g., setprop debug.sf.enable_layer_caching 1, or
       // adb shell service call SurfaceFlinger 1040 i32 1 [i64 <display ID>]
-      : mFlattener(base::GetBoolProperty(std::string("debug.sf.enable_hole_punch_pip"), true)) {
+      : mFlattener(renderEngine,
+                   base::GetBoolProperty(std::string("debug.sf.enable_hole_punch_pip"), true)) {
     mPredictorEnabled =
             base::GetBoolProperty(std::string("debug.sf.enable_planner_prediction"), false);
 }
@@ -160,10 +161,9 @@
                             finalPlan);
 }
 
-void Planner::renderCachedSets(renderengine::RenderEngine& renderEngine,
-                               const OutputCompositionState& outputState) {
+void Planner::renderCachedSets(const OutputCompositionState& outputState) {
     ATRACE_CALL();
-    mFlattener.renderCachedSets(renderEngine, outputState);
+    mFlattener.renderCachedSets(outputState);
 }
 
 void Planner::dump(const Vector<String16>& args, std::string& result) {
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/TexturePool.cpp b/services/surfaceflinger/CompositionEngine/src/planner/TexturePool.cpp
new file mode 100644
index 0000000..e3772a2
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/src/planner/TexturePool.cpp
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// #define LOG_NDEBUG 0
+
+#undef LOG_TAG
+#define LOG_TAG "Planner"
+
+#include <compositionengine/impl/planner/TexturePool.h>
+#include <utils/Log.h>
+
+namespace android::compositionengine::impl::planner {
+
+void TexturePool::setDisplaySize(ui::Size size) {
+    if (mSize == size) {
+        return;
+    }
+    mSize = size;
+    mPool.clear();
+    mPool.resize(kMinPoolSize);
+    std::generate_n(mPool.begin(), kMinPoolSize, [&]() { return Entry{genTexture(), nullptr}; });
+}
+
+std::shared_ptr<TexturePool::AutoTexture> TexturePool::borrowTexture() {
+    if (mPool.empty()) {
+        return std::make_shared<AutoTexture>(*this, genTexture(), nullptr);
+    }
+
+    const auto entry = mPool.front();
+    mPool.pop_front();
+    return std::make_shared<AutoTexture>(*this, entry.texture, entry.fence);
+}
+
+void TexturePool::returnTexture(std::shared_ptr<renderengine::ExternalTexture>&& texture,
+                                const sp<Fence>& fence) {
+    // Drop the texture on the floor if the pool is no longer tracking textures of the same size.
+    if (static_cast<int32_t>(texture->getBuffer()->getWidth()) != mSize.getWidth() ||
+        static_cast<int32_t>(texture->getBuffer()->getHeight()) != mSize.getHeight()) {
+        ALOGV("Deallocating texture from Planner's pool - display size changed (previous: (%dx%d), "
+              "current: (%dx%d))",
+              texture->getBuffer()->getWidth(), texture->getBuffer()->getHeight(), mSize.getWidth(),
+              mSize.getHeight());
+        return;
+    }
+
+    // Also ensure the pool does not grow beyond a maximum size.
+    if (mPool.size() == kMaxPoolSize) {
+        ALOGD("Deallocating texture from Planner's pool - max size [%" PRIu64 "] reached",
+              static_cast<uint64_t>(kMaxPoolSize));
+        return;
+    }
+
+    mPool.push_back({std::move(texture), fence});
+}
+
+std::shared_ptr<renderengine::ExternalTexture> TexturePool::genTexture() {
+    LOG_ALWAYS_FATAL_IF(!mSize.isValid(), "Attempted to generate texture with invalid size");
+    return std::make_shared<
+            renderengine::ExternalTexture>(sp<GraphicBuffer>::
+                                                   make(mSize.getWidth(), mSize.getHeight(),
+                                                        HAL_PIXEL_FORMAT_RGBA_8888, 1,
+                                                        GraphicBuffer::USAGE_HW_RENDER |
+                                                                GraphicBuffer::USAGE_HW_COMPOSER |
+                                                                GraphicBuffer::USAGE_HW_TEXTURE,
+                                                        "Planner"),
+                                           mRenderEngine,
+                                           renderengine::ExternalTexture::Usage::READABLE |
+                                                   renderengine::ExternalTexture::Usage::WRITEABLE);
+}
+
+} // namespace android::compositionengine::impl::planner
\ No newline at end of file
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
index 5bd1216..e9ecf3e 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
@@ -699,6 +699,7 @@
             Hwc2::IComposerClient::BlendMode::PREMULTIPLIED;
     static constexpr float kAlpha = 51.f;
     static constexpr float kOverrideAlpha = 1.f;
+    static constexpr float kSkipAlpha = 0.f;
     static constexpr ui::Dataspace kDataspace = static_cast<ui::Dataspace>(71);
     static constexpr ui::Dataspace kOverrideDataspace = static_cast<ui::Dataspace>(72);
     static constexpr int kSupportedPerFrameMetadata = 101;
@@ -1055,6 +1056,22 @@
                                  /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
 }
 
+TEST_F(OutputLayerWriteStateToHWCTest, overriddenSkipLayerDoesNotSendBuffer) {
+    mLayerFEState.compositionType = Hwc2::IComposerClient::Composition::DEVICE;
+    includeOverrideInfo();
+
+    expectGeometryCommonCalls(kOverrideDisplayFrame, kOverrideSourceCrop, kOverrideBufferTransform,
+                              kOverrideBlendMode, kSkipAlpha);
+    expectPerFrameCommonCalls(SimulateUnsupported::None, kOverrideDataspace, kOverrideVisibleRegion,
+                              kOverrideSurfaceDamage);
+    expectSetHdrMetadataAndBufferCalls();
+    expectSetCompositionTypeCall(Hwc2::IComposerClient::Composition::DEVICE);
+    EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillRepeatedly(Return(false));
+
+    mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ true, 0,
+                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
+}
+
 TEST_F(OutputLayerWriteStateToHWCTest, includesOverrideInfoIfPresent) {
     mLayerFEState.compositionType = Hwc2::IComposerClient::Composition::DEVICE;
     includeOverrideInfo();
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
index 52e0428..c381081 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
@@ -143,6 +143,7 @@
         mOutput->setRenderSurfaceForTest(std::unique_ptr<RenderSurface>(mRenderSurface));
 
         mOutput->editState().displaySpace.bounds = kDefaultDisplaySize;
+        EXPECT_CALL(mCompositionEngine, getRenderEngine()).WillRepeatedly(ReturnRef(mRenderEngine));
     }
 
     void injectOutputLayer(InjectedLayer& layer) {
@@ -156,6 +157,7 @@
     static const Rect kDefaultDisplaySize;
 
     StrictMock<mock::CompositionEngine> mCompositionEngine;
+    StrictMock<renderengine::mock::RenderEngine> mRenderEngine;
     mock::DisplayColorProfile* mDisplayColorProfile = new StrictMock<mock::DisplayColorProfile>();
     mock::RenderSurface* mRenderSurface = new StrictMock<mock::RenderSurface>();
     std::shared_ptr<Output> mOutput = createOutput(mCompositionEngine);
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp
index 8eeb0bf..4a76a95 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp
@@ -40,6 +40,7 @@
 using impl::planner::CachedSet;
 using impl::planner::LayerState;
 using impl::planner::LayerStateField;
+using impl::planner::TexturePool;
 
 namespace {
 
@@ -50,6 +51,7 @@
 
     return expectedBlurSetting == arg.blurSetting;
 }
+static const ui::Size kOutputSize = ui::Size(1, 1);
 
 class CachedSetTest : public testing::Test {
 public:
@@ -76,9 +78,11 @@
     impl::OutputCompositionState mOutputState;
 
     android::renderengine::mock::RenderEngine mRenderEngine;
+    TexturePool mTexturePool = TexturePool(mRenderEngine);
 };
 
 void CachedSetTest::SetUp() {
+    mTexturePool.setDisplaySize(kOutputSize);
     for (size_t i = 0; i < kNumLayers; i++) {
         auto testLayer = std::make_unique<TestLayer>();
         auto pos = static_cast<int32_t>(i);
@@ -319,7 +323,7 @@
                                 const std::vector<const renderengine::LayerSettings*>& layers,
                                 const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
                                 base::unique_fd&&, base::unique_fd*) -> size_t {
-        EXPECT_EQ(Rect(-1, -1, 9, 4), displaySettings.physicalDisplay);
+        EXPECT_EQ(mOutputState.framebufferSpace.content, displaySettings.physicalDisplay);
         EXPECT_EQ(mOutputState.layerStackSpace.content, displaySettings.clip);
         EXPECT_EQ(ui::Transform::toRotationFlags(mOutputState.framebufferSpace.orientation),
                   displaySettings.orientation);
@@ -333,10 +337,11 @@
     EXPECT_CALL(*layerFE1, prepareClientCompositionList(_)).WillOnce(Return(clientCompList1));
     EXPECT_CALL(*layerFE2, prepareClientCompositionList(_)).WillOnce(Return(clientCompList2));
     EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).WillOnce(Invoke(drawLayers));
-    cachedSet.render(mRenderEngine, mOutputState);
+    cachedSet.render(mRenderEngine, mTexturePool, mOutputState);
     expectReadyBuffer(cachedSet);
 
     EXPECT_EQ(mOutputState.framebufferSpace, cachedSet.getOutputSpace());
+    EXPECT_EQ(mOutputState.framebufferSpace.content, cachedSet.getTextureBounds());
 
     // Now check that appending a new cached set properly cleans up RenderEngine resources.
     CachedSet::Layer& layer3 = *mTestLayers[2]->cachedSetLayer.get();
@@ -367,7 +372,7 @@
                                 const std::vector<const renderengine::LayerSettings*>& layers,
                                 const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
                                 base::unique_fd&&, base::unique_fd*) -> size_t {
-        EXPECT_EQ(Rect(1, 2, 9, 4), displaySettings.physicalDisplay);
+        EXPECT_EQ(mOutputState.framebufferSpace.content, displaySettings.physicalDisplay);
         EXPECT_EQ(mOutputState.layerStackSpace.content, displaySettings.clip);
         EXPECT_EQ(ui::Transform::toRotationFlags(mOutputState.framebufferSpace.orientation),
                   displaySettings.orientation);
@@ -381,7 +386,7 @@
     EXPECT_CALL(*layerFE1, prepareClientCompositionList(_)).WillOnce(Return(clientCompList1));
     EXPECT_CALL(*layerFE2, prepareClientCompositionList(_)).WillOnce(Return(clientCompList2));
     EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).WillOnce(Invoke(drawLayers));
-    cachedSet.render(mRenderEngine, mOutputState);
+    cachedSet.render(mRenderEngine, mTexturePool, mOutputState);
     expectReadyBuffer(cachedSet);
 
     EXPECT_EQ(mOutputState.framebufferSpace, cachedSet.getOutputSpace());
@@ -424,6 +429,20 @@
     EXPECT_FALSE(cachedSet.requiresHolePunch());
 }
 
+TEST_F(CachedSetTest, holePunch_requiresNonHdr) {
+    mTestLayers[0]->outputLayerCompositionState.dataspace = ui::Dataspace::BT2020_PQ;
+    mTestLayers[0]->layerState->update(&mTestLayers[0]->outputLayer);
+
+    CachedSet::Layer& layer = *mTestLayers[0]->cachedSetLayer.get();
+    mTestLayers[0]->layerFECompositionState.buffer = sp<GraphicBuffer>::make();
+    sp<mock::LayerFE> layerFE = mTestLayers[0]->layerFE;
+
+    CachedSet cachedSet(layer);
+    EXPECT_CALL(*layerFE, hasRoundedCorners()).WillRepeatedly(Return(true));
+
+    EXPECT_FALSE(cachedSet.requiresHolePunch());
+}
+
 TEST_F(CachedSetTest, requiresHolePunch) {
     CachedSet::Layer& layer = *mTestLayers[0]->cachedSetLayer.get();
     mTestLayers[0]->layerFECompositionState.buffer = sp<GraphicBuffer>::make();
@@ -554,7 +573,7 @@
     };
 
     EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).WillOnce(Invoke(drawLayers));
-    cachedSet.render(mRenderEngine, mOutputState);
+    cachedSet.render(mRenderEngine, mTexturePool, mOutputState);
 }
 
 TEST_F(CachedSetTest, addHolePunch_noBuffer) {
@@ -604,7 +623,7 @@
     };
 
     EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).WillOnce(Invoke(drawLayers));
-    cachedSet.render(mRenderEngine, mOutputState);
+    cachedSet.render(mRenderEngine, mTexturePool, mOutputState);
 }
 
 TEST_F(CachedSetTest, append_removesHolePunch) {
@@ -741,7 +760,7 @@
     };
 
     EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).WillOnce(Invoke(drawLayers));
-    cachedSet.render(mRenderEngine, mOutputState);
+    cachedSet.render(mRenderEngine, mTexturePool, mOutputState);
 }
 
 } // namespace
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
index 8b03964..e176c98 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
@@ -46,13 +46,14 @@
 
 class TestableFlattener : public Flattener {
 public:
-    TestableFlattener(bool enableHolePunch) : Flattener(enableHolePunch) {}
+    TestableFlattener(renderengine::RenderEngine& renderEngine, bool enableHolePunch)
+          : Flattener(renderEngine, enableHolePunch) {}
     const std::optional<CachedSet>& getNewCachedSetForTesting() const { return mNewCachedSet; }
 };
 
 class FlattenerTest : public testing::Test {
 public:
-    FlattenerTest() : mFlattener(std::make_unique<TestableFlattener>(true)) {}
+    FlattenerTest() : mFlattener(std::make_unique<TestableFlattener>(mRenderEngine, true)) {}
     void SetUp() override;
 
 protected:
@@ -60,7 +61,7 @@
     void initializeFlattener(const std::vector<const LayerState*>& layers);
     void expectAllLayersFlattened(const std::vector<const LayerState*>& layers);
 
-    // mRenderEngine may be held as a pointer to mFlattener, so mFlattener must be destroyed first.
+    // mRenderEngine is held as a reference in mFlattener, so mFlattener must be destroyed first.
     renderengine::mock::RenderEngine mRenderEngine;
     std::unique_ptr<TestableFlattener> mFlattener;
 
@@ -84,6 +85,7 @@
 };
 
 void FlattenerTest::SetUp() {
+    mFlattener->setDisplaySize({1, 1});
     for (size_t i = 0; i < kNumLayers; i++) {
         auto testLayer = std::make_unique<TestLayer>();
         auto pos = static_cast<int32_t>(i);
@@ -146,13 +148,13 @@
     initializeOverrideBuffer(layers);
     EXPECT_EQ(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
+    mFlattener->renderCachedSets(mOutputState);
 
     // same geometry, update the internal layer stack
     initializeOverrideBuffer(layers);
     EXPECT_EQ(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
+    mFlattener->renderCachedSets(mOutputState);
 }
 
 void FlattenerTest::expectAllLayersFlattened(const std::vector<const LayerState*>& layers) {
@@ -162,7 +164,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_EQ(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
+    mFlattener->renderCachedSets(mOutputState);
 
     for (const auto layer : layers) {
         EXPECT_EQ(nullptr, layer->getOutputLayer()->getState().overrideInfo.buffer);
@@ -172,7 +174,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
+    mFlattener->renderCachedSets(mOutputState);
 
     const auto buffer = layers[0]->getOutputLayer()->getState().overrideInfo.buffer;
     EXPECT_NE(nullptr, buffer);
@@ -207,7 +209,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_EQ(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
+    mFlattener->renderCachedSets(mOutputState);
 }
 
 TEST_F(FlattenerTest, flattenLayers_basicFlatten) {
@@ -253,7 +255,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
+    mFlattener->renderCachedSets(mOutputState);
 
     EXPECT_NE(nullptr, overrideBuffer1);
     EXPECT_EQ(overrideBuffer1, overrideBuffer2);
@@ -358,7 +360,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_EQ(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
+    mFlattener->renderCachedSets(mOutputState);
 
     EXPECT_EQ(nullptr, overrideBuffer1);
     EXPECT_EQ(nullptr, overrideBuffer2);
@@ -395,7 +397,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_EQ(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
+    mFlattener->renderCachedSets(mOutputState);
 
     EXPECT_EQ(nullptr, overrideBuffer1);
     EXPECT_EQ(nullptr, overrideBuffer2);
@@ -404,7 +406,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
+    mFlattener->renderCachedSets(mOutputState);
 
     EXPECT_EQ(nullptr, overrideBuffer1);
     EXPECT_NE(nullptr, overrideBuffer2);
@@ -417,7 +419,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
+    mFlattener->renderCachedSets(mOutputState);
 
     EXPECT_EQ(nullptr, overrideBuffer1);
     EXPECT_NE(nullptr, overrideBuffer2);
@@ -426,7 +428,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
+    mFlattener->renderCachedSets(mOutputState);
 
     EXPECT_NE(nullptr, overrideBuffer1);
     EXPECT_EQ(overrideBuffer1, overrideBuffer2);
@@ -468,7 +470,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_EQ(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
+    mFlattener->renderCachedSets(mOutputState);
 
     EXPECT_EQ(nullptr, overrideBuffer1);
     EXPECT_EQ(nullptr, overrideBuffer2);
@@ -482,7 +484,7 @@
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
     mOutputState.framebufferSpace.orientation = ui::ROTATION_90;
-    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
+    mFlattener->renderCachedSets(mOutputState);
 
     EXPECT_NE(nullptr, overrideBuffer1);
     EXPECT_EQ(overrideBuffer1, overrideBuffer2);
@@ -495,7 +497,7 @@
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
     mOutputState.framebufferSpace.orientation = ui::ROTATION_180;
-    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
+    mFlattener->renderCachedSets(mOutputState);
 
     EXPECT_NE(nullptr, overrideBuffer1);
     EXPECT_EQ(overrideBuffer1, overrideBuffer2);
@@ -510,7 +512,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
+    mFlattener->renderCachedSets(mOutputState);
 
     EXPECT_NE(nullptr, overrideBuffer1);
     EXPECT_EQ(overrideBuffer1, overrideBuffer2);
@@ -522,7 +524,7 @@
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
     mOutputState.framebufferSpace.orientation = ui::ROTATION_270;
-    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
+    mFlattener->renderCachedSets(mOutputState);
 
     EXPECT_NE(nullptr, overrideBuffer1);
     EXPECT_EQ(overrideBuffer1, overrideBuffer2);
@@ -561,7 +563,7 @@
 
     // This will render a CachedSet.
     EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).WillOnce(Return(NO_ERROR));
-    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
+    mFlattener->renderCachedSets(mOutputState);
 
     // We've rendered a CachedSet, but we haven't merged it in.
     EXPECT_EQ(nullptr, overrideBuffer1);
@@ -574,7 +576,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
+    mFlattener->renderCachedSets(mOutputState);
 
     EXPECT_NE(nullptr, overrideBuffer1);
     EXPECT_EQ(overrideBuffer1, overrideBuffer2);
@@ -623,7 +625,7 @@
 
     // This will render a CachedSet.
     EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).WillOnce(Return(NO_ERROR));
-    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
+    mFlattener->renderCachedSets(mOutputState);
 
     // We've rendered a CachedSet, but we haven't merged it in.
     EXPECT_EQ(nullptr, overrideBuffer1);
@@ -636,7 +638,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
+    mFlattener->renderCachedSets(mOutputState);
 
     EXPECT_NE(nullptr, overrideBuffer1);
     EXPECT_EQ(overrideBuffer1, overrideBuffer2);
@@ -680,7 +682,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_EQ(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
+    mFlattener->renderCachedSets(mOutputState);
 
     for (const auto layer : layers) {
         EXPECT_EQ(nullptr, layer->getOutputLayer()->getState().overrideInfo.buffer);
@@ -690,7 +692,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
+    mFlattener->renderCachedSets(mOutputState);
     EXPECT_NE(nullptr, overrideBuffer1);
     EXPECT_EQ(overrideBuffer1, overrideBuffer2);
     EXPECT_EQ(nullptr, overrideBuffer3);
@@ -724,7 +726,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_EQ(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
+    mFlattener->renderCachedSets(mOutputState);
 
     for (const auto layer : layers) {
         EXPECT_EQ(nullptr, layer->getOutputLayer()->getState().overrideInfo.buffer);
@@ -735,7 +737,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_EQ(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
+    mFlattener->renderCachedSets(mOutputState);
     for (const auto layer : layers) {
         EXPECT_EQ(nullptr, layer->getOutputLayer()->getState().overrideInfo.buffer);
     }
@@ -776,7 +778,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_EQ(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
+    mFlattener->renderCachedSets(mOutputState);
 
     for (const auto layer : layers) {
         EXPECT_EQ(nullptr, layer->getOutputLayer()->getState().overrideInfo.buffer);
@@ -786,7 +788,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
+    mFlattener->renderCachedSets(mOutputState);
     EXPECT_EQ(nullptr, overrideBuffer1);
     EXPECT_EQ(nullptr, blurOverrideBuffer);
     EXPECT_NE(nullptr, overrideBuffer3);
@@ -823,7 +825,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_EQ(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
+    mFlattener->renderCachedSets(mOutputState);
 
     const auto& cachedSet = mFlattener->getNewCachedSetForTesting();
     ASSERT_NE(std::nullopt, cachedSet);
@@ -837,7 +839,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
+    mFlattener->renderCachedSets(mOutputState);
     EXPECT_NE(nullptr, overrideBuffer1);
     EXPECT_EQ(overrideBuffer2, overrideBuffer1);
     EXPECT_EQ(nullptr, blurOverrideBuffer);
@@ -864,7 +866,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_EQ(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
+    mFlattener->renderCachedSets(mOutputState);
 
     EXPECT_EQ(nullptr, overrideBuffer1);
     EXPECT_EQ(nullptr, overrideBuffer2);
@@ -872,12 +874,12 @@
     // Simulate attempting to render prior to merging the new cached set with the layer stack.
     // Here we should not try to re-render.
     EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).Times(0);
-    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
+    mFlattener->renderCachedSets(mOutputState);
 
     // We provide the override buffer now that it's rendered
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
+    mFlattener->renderCachedSets(mOutputState);
 
     EXPECT_NE(nullptr, overrideBuffer1);
     EXPECT_EQ(overrideBuffer2, overrideBuffer1);
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/TexturePoolTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/TexturePoolTest.cpp
new file mode 100644
index 0000000..b802e51
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/TexturePoolTest.cpp
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "TexturePoolTest"
+
+#include <compositionengine/impl/planner/TexturePool.h>
+#include <gtest/gtest.h>
+#include <log/log.h>
+#include <renderengine/mock/RenderEngine.h>
+
+namespace android::compositionengine::impl::planner {
+namespace {
+
+const ui::Size kDisplaySize(1, 1);
+const ui::Size kDisplaySizeTwo(2, 2);
+
+class TestableTexturePool : public TexturePool {
+public:
+    TestableTexturePool(renderengine::RenderEngine& renderEngine) : TexturePool(renderEngine) {}
+
+    size_t getMinPoolSize() const { return kMinPoolSize; }
+    size_t getMaxPoolSize() const { return kMaxPoolSize; }
+    size_t getPoolSize() const { return mPool.size(); }
+};
+
+struct TexturePoolTest : public testing::Test {
+    TexturePoolTest() {
+        const ::testing::TestInfo* const test_info =
+                ::testing::UnitTest::GetInstance()->current_test_info();
+        ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
+        mTexturePool.setDisplaySize(kDisplaySize);
+    }
+
+    ~TexturePoolTest() {
+        const ::testing::TestInfo* const test_info =
+                ::testing::UnitTest::GetInstance()->current_test_info();
+        ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
+    }
+
+    renderengine::mock::RenderEngine mRenderEngine;
+    TestableTexturePool mTexturePool = TestableTexturePool(mRenderEngine);
+};
+
+TEST_F(TexturePoolTest, preallocatesMinPool) {
+    EXPECT_EQ(mTexturePool.getMinPoolSize(), mTexturePool.getPoolSize());
+}
+
+TEST_F(TexturePoolTest, doesNotAllocateBeyondMinPool) {
+    for (size_t i = 0; i < mTexturePool.getMinPoolSize() + 1; i++) {
+        auto texture = mTexturePool.borrowTexture();
+    }
+
+    EXPECT_EQ(mTexturePool.getMinPoolSize(), mTexturePool.getPoolSize());
+}
+
+TEST_F(TexturePoolTest, cyclesUpToMaxPoolSize) {
+    std::unordered_set<uint64_t> bufferIds;
+    std::deque<std::shared_ptr<TexturePool::AutoTexture>> textures;
+    for (size_t i = 0; i < mTexturePool.getMaxPoolSize(); i++) {
+        textures.emplace_back(mTexturePool.borrowTexture());
+        bufferIds.insert(textures.back()->get()->getBuffer()->getId());
+    }
+
+    EXPECT_EQ(mTexturePool.getMaxPoolSize(), bufferIds.size());
+
+    for (size_t i = 0; i < 3; i++) {
+        textures.pop_front();
+        textures.emplace_back(mTexturePool.borrowTexture());
+        bufferIds.insert(textures.back()->get()->getBuffer()->getId());
+    }
+
+    EXPECT_EQ(mTexturePool.getMaxPoolSize(), bufferIds.size());
+}
+
+TEST_F(TexturePoolTest, goesPastMaxSizeAndRebounds) {
+    std::unordered_set<uint64_t> bufferIds;
+    std::vector<std::shared_ptr<TexturePool::AutoTexture>> textures;
+    for (size_t i = 0; i < mTexturePool.getMaxPoolSize() + 2; i++) {
+        textures.emplace_back(mTexturePool.borrowTexture());
+        bufferIds.insert(textures.back()->get()->getBuffer()->getId());
+    }
+
+    EXPECT_EQ(mTexturePool.getMaxPoolSize() + 2, bufferIds.size());
+
+    // Return the textures to the pool.
+    // Now when we cycle through the pool it's again bounded by max textures.
+    textures.clear();
+
+    std::unordered_set<uint64_t> newBufferIds;
+    for (size_t i = 0; i < 2 * mTexturePool.getMaxPoolSize(); i++) {
+        auto texture = mTexturePool.borrowTexture();
+        newBufferIds.insert(texture->get()->getBuffer()->getId());
+    }
+
+    EXPECT_EQ(mTexturePool.getMaxPoolSize(), newBufferIds.size());
+}
+
+TEST_F(TexturePoolTest, reallocatesWhenDisplaySizeChanges) {
+    auto texture = mTexturePool.borrowTexture();
+
+    EXPECT_EQ(kDisplaySize.getWidth(),
+              static_cast<int32_t>(texture->get()->getBuffer()->getWidth()));
+    EXPECT_EQ(kDisplaySize.getHeight(),
+              static_cast<int32_t>(texture->get()->getBuffer()->getHeight()));
+    mTexturePool.setDisplaySize(kDisplaySizeTwo);
+
+    EXPECT_EQ(mTexturePool.getMinPoolSize(), mTexturePool.getPoolSize());
+    texture.reset();
+    // When the texture is returned to the pool, the pool now destroys it.
+    EXPECT_EQ(mTexturePool.getMinPoolSize(), mTexturePool.getPoolSize());
+
+    texture = mTexturePool.borrowTexture();
+    EXPECT_EQ(kDisplaySizeTwo.getWidth(),
+              static_cast<int32_t>(texture->get()->getBuffer()->getWidth()));
+    EXPECT_EQ(kDisplaySizeTwo.getHeight(),
+              static_cast<int32_t>(texture->get()->getBuffer()->getHeight()));
+}
+
+} // namespace
+} // namespace android::compositionengine::impl::planner
diff --git a/services/vibratorservice/VibratorHalWrapper.cpp b/services/vibratorservice/VibratorHalWrapper.cpp
index 1010aa5..f15a963 100644
--- a/services/vibratorservice/VibratorHalWrapper.cpp
+++ b/services/vibratorservice/VibratorHalWrapper.cpp
@@ -95,7 +95,10 @@
 }
 
 HalResult<void> HalResult<void>::fromStatus(binder::Status status) {
-    if (status.exceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION) {
+    if (status.exceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION ||
+        status.transactionError() == android::UNKNOWN_TRANSACTION) {
+        // UNKNOWN_TRANSACTION means the HAL implementation is an older version, so this is
+        // the same as the operation being unsupported by this HAL. Should not retry.
         return HalResult<void>::unsupported();
     }
     if (status.isOk()) {
diff --git a/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h b/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h
index 8720d9d..87bc34e 100644
--- a/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h
+++ b/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h
@@ -42,7 +42,10 @@
     static HalResult<T> unsupported() { return HalResult("", /* unsupported= */ true); }
 
     static HalResult<T> fromStatus(binder::Status status, T data) {
-        if (status.exceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION) {
+        if (status.exceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION ||
+            status.transactionError() == android::UNKNOWN_TRANSACTION) {
+            // UNKNOWN_TRANSACTION means the HAL implementation is an older version, so this is
+            // the same as the operation being unsupported by this HAL. Should not retry.
             return HalResult<T>::unsupported();
         }
         if (status.isOk()) {
diff --git a/services/vibratorservice/test/VibratorHalWrapperAidlTest.cpp b/services/vibratorservice/test/VibratorHalWrapperAidlTest.cpp
index af0cdb8..7813303 100644
--- a/services/vibratorservice/test/VibratorHalWrapperAidlTest.cpp
+++ b/services/vibratorservice/test/VibratorHalWrapperAidlTest.cpp
@@ -189,8 +189,7 @@
                 .WillRepeatedly(vibrator::TriggerSchedulerCallback());
         EXPECT_CALL(*mMockHal.get(), on(Eq(11), _))
                 .Times(Exactly(1))
-                .WillRepeatedly(Return(
-                        Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)));
+                .WillRepeatedly(Return(Status::fromStatusT(UNKNOWN_TRANSACTION)));
         EXPECT_CALL(*mMockHal.get(), on(Eq(12), _))
                 .Times(Exactly(1))
                 .WillRepeatedly(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)));
@@ -228,8 +227,7 @@
         EXPECT_CALL(*mMockHal.get(), setAmplitude(Eq(0.1f))).Times(Exactly(1));
         EXPECT_CALL(*mMockHal.get(), setAmplitude(Eq(0.2f)))
                 .Times(Exactly(1))
-                .WillRepeatedly(Return(
-                        Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)));
+                .WillRepeatedly(Return(Status::fromStatusT(UNKNOWN_TRANSACTION)));
         EXPECT_CALL(*mMockHal.get(), setAmplitude(Eq(0.5f)))
                 .Times(Exactly(1))
                 .WillRepeatedly(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)));
@@ -265,8 +263,7 @@
         EXPECT_CALL(*mMockHal.get(),
                     alwaysOnEnable(Eq(2), Eq(Effect::TICK), Eq(EffectStrength::MEDIUM)))
                 .Times(Exactly(1))
-                .WillRepeatedly(Return(
-                        Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)));
+                .WillRepeatedly(Return(Status::fromStatusT(UNKNOWN_TRANSACTION)));
         EXPECT_CALL(*mMockHal.get(),
                     alwaysOnEnable(Eq(3), Eq(Effect::POP), Eq(EffectStrength::STRONG)))
                 .Times(Exactly(1))
@@ -397,8 +394,7 @@
                     Return(Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)));
     EXPECT_CALL(*mMockHal.get(), getSupportedPrimitives(_))
             .Times(Exactly(1))
-            .WillRepeatedly(
-                    Return(Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)));
+            .WillRepeatedly(Return(Status::fromStatusT(UNKNOWN_TRANSACTION)));
     EXPECT_CALL(*mMockHal.get(), getFrequencyMinimum(_))
             .Times(Exactly(1))
             .WillRepeatedly(DoAll(SetArgPointee<0>(F_MIN), Return(Status())));
@@ -411,8 +407,7 @@
                     Return(Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)));
     EXPECT_CALL(*mMockHal.get(), getBandwidthAmplitudeMap(_))
             .Times(Exactly(1))
-            .WillRepeatedly(
-                    Return(Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)));
+            .WillRepeatedly(Return(Status::fromStatusT(UNKNOWN_TRANSACTION)));
     EXPECT_CALL(*mMockHal.get(), getSupportedBraking(_))
             .Times(Exactly(1))
             .WillRepeatedly(
@@ -451,8 +446,7 @@
                         DoAll(SetArgPointee<3>(1000), TriggerCallbackInArg2(), Return(Status())));
         EXPECT_CALL(*mMockHal.get(), perform(Eq(Effect::POP), Eq(EffectStrength::MEDIUM), _, _))
                 .Times(Exactly(1))
-                .WillRepeatedly(Return(
-                        Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)));
+                .WillRepeatedly(Return(Status::fromStatusT(UNKNOWN_TRANSACTION)));
         EXPECT_CALL(*mMockHal.get(), perform(Eq(Effect::THUD), Eq(EffectStrength::STRONG), _, _))
                 .Times(Exactly(1))
                 .WillRepeatedly(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)));
@@ -549,8 +543,7 @@
                 .WillRepeatedly(DoAll(TriggerCallbackInArg1(), Return(Status())));
         EXPECT_CALL(*mMockHal.get(), compose(Eq(singleEffect), _))
                 .Times(Exactly(1))
-                .WillRepeatedly(Return(
-                        Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)));
+                .WillRepeatedly(Return(Status::fromStatusT(UNKNOWN_TRANSACTION)));
         EXPECT_CALL(*mMockHal.get(), compose(Eq(multipleEffects), _))
                 .Times(Exactly(1))
                 .WillRepeatedly(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)));
diff --git a/services/vibratorservice/test/VibratorManagerHalWrapperAidlTest.cpp b/services/vibratorservice/test/VibratorManagerHalWrapperAidlTest.cpp
index 548d028..1593cb1 100644
--- a/services/vibratorservice/test/VibratorManagerHalWrapperAidlTest.cpp
+++ b/services/vibratorservice/test/VibratorManagerHalWrapperAidlTest.cpp
@@ -308,8 +308,7 @@
                                       Return(Status())));
         EXPECT_CALL(*mMockHal.get(), triggerSynced(_))
                 .Times(Exactly(3))
-                .WillOnce(Return(
-                        Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)))
+                .WillOnce(Return(Status::fromStatusT(UNKNOWN_TRANSACTION)))
                 .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
                 .WillRepeatedly(DoAll(TriggerCallback(), Return(Status())));
     }
@@ -345,8 +344,7 @@
 TEST_F(VibratorManagerHalWrapperAidlTest, TestCancelSynced) {
     EXPECT_CALL(*mMockHal.get(), cancelSynced())
             .Times(Exactly(3))
-            .WillOnce(
-                    Return(Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)))
+            .WillOnce(Return(Status::fromStatusT(UNKNOWN_TRANSACTION)))
             .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
             .WillRepeatedly(Return(Status()));