Merge "Added libgui_parcelable_fuzzer"
diff --git a/libs/binder/tests/unit_fuzzers/BpBinderFuzz.cpp b/libs/binder/tests/unit_fuzzers/BpBinderFuzz.cpp
index e77c55c..910c9dc 100644
--- a/libs/binder/tests/unit_fuzzers/BpBinderFuzz.cpp
+++ b/libs/binder/tests/unit_fuzzers/BpBinderFuzz.cpp
@@ -49,6 +49,7 @@
     });
 
     sp<RpcSession> session = RpcSession::make();
+    session->setMaxIncomingThreads(1);
     status_t status;
     for (size_t tries = 0; tries < 5; tries++) {
         usleep(10000);
diff --git a/libs/gui/tests/EndToEndNativeInputTest.cpp b/libs/gui/tests/EndToEndNativeInputTest.cpp
index c6cdeb7..2637f59 100644
--- a/libs/gui/tests/EndToEndNativeInputTest.cpp
+++ b/libs/gui/tests/EndToEndNativeInputTest.cpp
@@ -1184,18 +1184,23 @@
     std::vector<sp<IGraphicBufferProducer>> mProducers;
 };
 
-TEST_F(MultiDisplayTests, drop_input_if_layer_on_invalid_display) {
+TEST_F(MultiDisplayTests, drop_touch_if_layer_on_invalid_display) {
     ui::LayerStack layerStack = ui::LayerStack::fromValue(42);
     // Do not create a display associated with the LayerStack.
     std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
     surface->doTransaction([&](auto &t, auto &sc) { t.setLayerStack(sc, layerStack); });
     surface->showAt(100, 100);
 
+    // Touches should be dropped if the layer is on an invalid display.
     injectTapOnDisplay(101, 101, layerStack.id);
-    surface->requestFocus(layerStack.id);
-    injectKeyOnDisplay(AKEYCODE_V, layerStack.id);
-
     EXPECT_EQ(surface->consumeEvent(100), nullptr);
+
+    // However, we still let the window be focused and receive keys.
+    surface->requestFocus(layerStack.id);
+    surface->assertFocusChange(true);
+
+    injectKeyOnDisplay(AKEYCODE_V, layerStack.id);
+    surface->expectKey(AKEYCODE_V);
 }
 
 TEST_F(MultiDisplayTests, virtual_display_receives_input) {
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
index a0bba59..347b8b7 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
@@ -24,43 +24,11 @@
 #include <EGL/egl.h>
 #include <EGL/eglext.h>
 #include <GrContextOptions.h>
-#include <SkBlendMode.h>
-#include <SkCanvas.h>
-#include <SkColor.h>
-#include <SkColorFilter.h>
-#include <SkColorMatrix.h>
-#include <SkColorSpace.h>
-#include <SkData.h>
-#include <SkGraphics.h>
-#include <SkImage.h>
-#include <SkImageFilters.h>
-#include <SkImageInfo.h>
-#include <SkM44.h>
-#include <SkMatrix.h>
-#include <SkPaint.h>
-#include <SkPath.h>
-#include <SkPoint.h>
-#include <SkPoint3.h>
-#include <SkRect.h>
-#include <SkRefCnt.h>
-#include <SkRegion.h>
-#include <SkRRect.h>
-#include <SkRuntimeEffect.h>
-#include <SkSamplingOptions.h>
-#include <SkScalar.h>
-#include <SkShader.h>
-#include <SkShadowUtils.h>
-#include <SkString.h>
-#include <SkSurface.h>
-#include <SkTileMode.h>
 #include <android-base/stringprintf.h>
 #include <gl/GrGLInterface.h>
 #include <gui/TraceUtils.h>
 #include <sync/sync.h>
-#include <ui/BlurRegion.h>
-#include <ui/DataspaceUtils.h>
 #include <ui/DebugUtils.h>
-#include <ui/GraphicBuffer.h>
 #include <utils/Trace.h>
 
 #include <cmath>
@@ -69,23 +37,7 @@
 #include <numeric>
 
 #include "../gl/GLExtensions.h"
-#include "Cache.h"
-#include "ColorSpaces.h"
-#include "filters/BlurFilter.h"
-#include "filters/GaussianBlurFilter.h"
-#include "filters/KawaseBlurFilter.h"
-#include "filters/LinearEffect.h"
 #include "log/log_main.h"
-#include "skia/debug/SkiaCapture.h"
-#include "skia/debug/SkiaMemoryReporter.h"
-#include "skia/filters/StretchShaderFactory.h"
-#include "system/graphics-base-v1.0.h"
-
-namespace {
-// Debugging settings
-static const bool kPrintLayerSettings = false;
-static const bool kFlushAfterEveryLayer = kPrintLayerSettings;
-} // namespace
 
 bool checkGlError(const char* op, int lineNumber);
 
@@ -244,6 +196,7 @@
     std::unique_ptr<SkiaGLRenderEngine> engine =
             std::make_unique<SkiaGLRenderEngine>(args, display, ctxt, placeholder, protectedContext,
                                                  protectedPlaceholder);
+    engine->ensureGrContextsCreated();
 
     ALOGI("OpenGL ES informations:");
     ALOGI("vendor    : %s", extensions.getVendor());
@@ -256,11 +209,6 @@
     return engine;
 }
 
-std::future<void> SkiaGLRenderEngine::primeCache() {
-    Cache::primeShaderCache(this);
-    return {};
-}
-
 EGLConfig SkiaGLRenderEngine::chooseEglConfig(EGLDisplay display, int format, bool logConfig) {
     status_t err;
     EGLConfig config;
@@ -300,72 +248,20 @@
     return config;
 }
 
-sk_sp<SkData> SkiaGLRenderEngine::SkSLCacheMonitor::load(const SkData& key) {
-    // This "cache" does not actually cache anything. It just allows us to
-    // monitor Skia's internal cache. So this method always returns null.
-    return nullptr;
-}
-
-void SkiaGLRenderEngine::SkSLCacheMonitor::store(const SkData& key, const SkData& data,
-                                                 const SkString& description) {
-    mShadersCachedSinceLastCall++;
-    mTotalShadersCompiled++;
-    ATRACE_FORMAT("SF cache: %i shaders", mTotalShadersCompiled);
-}
-
-int SkiaGLRenderEngine::reportShadersCompiled() {
-    return mSkSLCacheMonitor.totalShadersCompiled();
-}
-
 SkiaGLRenderEngine::SkiaGLRenderEngine(const RenderEngineCreationArgs& args, EGLDisplay display,
                                        EGLContext ctxt, EGLSurface placeholder,
                                        EGLContext protectedContext, EGLSurface protectedPlaceholder)
-      : SkiaRenderEngine(args.renderEngineType),
+      : SkiaRenderEngine(args.renderEngineType,
+                         static_cast<PixelFormat>(args.pixelFormat),
+                         args.useColorManagement, args.supportsBackgroundBlur),
         mEGLDisplay(display),
         mEGLContext(ctxt),
         mPlaceholderSurface(placeholder),
         mProtectedEGLContext(protectedContext),
-        mProtectedPlaceholderSurface(protectedPlaceholder),
-        mDefaultPixelFormat(static_cast<PixelFormat>(args.pixelFormat)),
-        mUseColorManagement(args.useColorManagement) {
-    sk_sp<const GrGLInterface> glInterface = GrGLMakeNativeInterface();
-    LOG_ALWAYS_FATAL_IF(!glInterface.get());
-
-    GrContextOptions options;
-    options.fDisableDriverCorrectnessWorkarounds = true;
-    options.fDisableDistanceFieldPaths = true;
-    options.fReducedShaderVariations = true;
-    options.fPersistentCache = &mSkSLCacheMonitor;
-    mGrContext = GrDirectContext::MakeGL(glInterface, options);
-    if (supportsProtectedContent()) {
-        useProtectedContext(true);
-        mProtectedGrContext = GrDirectContext::MakeGL(glInterface, options);
-        useProtectedContext(false);
-    }
-
-    if (args.supportsBackgroundBlur) {
-        ALOGD("Background Blurs Enabled");
-        mBlurFilter = new KawaseBlurFilter();
-    }
-    mCapture = std::make_unique<SkiaCapture>();
-}
+        mProtectedPlaceholderSurface(protectedPlaceholder) { }
 
 SkiaGLRenderEngine::~SkiaGLRenderEngine() {
-    std::lock_guard<std::mutex> lock(mRenderingMutex);
-    if (mBlurFilter) {
-        delete mBlurFilter;
-    }
-
-    mCapture = nullptr;
-
-    mGrContext->flushAndSubmit(true);
-    mGrContext->abandonContext();
-
-    if (mProtectedGrContext) {
-        mProtectedGrContext->flushAndSubmit(true);
-        mProtectedGrContext->abandonContext();
-    }
-
+    finishRenderingAndAbandonContext();
     if (mPlaceholderSurface != EGL_NO_SURFACE) {
         eglDestroySurface(mEGLDisplay, mPlaceholderSurface);
     }
@@ -383,71 +279,69 @@
     eglReleaseThread();
 }
 
-bool SkiaGLRenderEngine::supportsProtectedContent() const {
+SkiaRenderEngine::Contexts SkiaGLRenderEngine::createDirectContexts(
+    const GrContextOptions& options) {
+
+    LOG_ALWAYS_FATAL_IF(isProtected(),
+                        "Cannot setup contexts while already in protected mode");
+
+    sk_sp<const GrGLInterface> glInterface = GrGLMakeNativeInterface();
+
+    LOG_ALWAYS_FATAL_IF(!glInterface.get(), "GrGLMakeNativeInterface() failed");
+
+    SkiaRenderEngine::Contexts contexts;
+    contexts.first = GrDirectContext::MakeGL(glInterface, options);
+    if (supportsProtectedContentImpl()) {
+        useProtectedContextImpl(GrProtected::kYes);
+        contexts.second = GrDirectContext::MakeGL(glInterface, options);
+        useProtectedContextImpl(GrProtected::kNo);
+    }
+
+    return contexts;
+}
+
+bool SkiaGLRenderEngine::supportsProtectedContentImpl() const {
     return mProtectedEGLContext != EGL_NO_CONTEXT;
 }
 
-GrDirectContext* SkiaGLRenderEngine::getActiveGrContext() const {
-    return mInProtectedContext ? mProtectedGrContext.get() : mGrContext.get();
-}
-
-void SkiaGLRenderEngine::useProtectedContext(bool useProtectedContext) {
-    if (useProtectedContext == mInProtectedContext ||
-        (useProtectedContext && !supportsProtectedContent())) {
-        return;
-    }
-
-    // release any scratch resources before switching into a new mode
-    if (getActiveGrContext()) {
-        getActiveGrContext()->purgeUnlockedResources(true);
-    }
-
+bool SkiaGLRenderEngine::useProtectedContextImpl(GrProtected isProtected) {
     const EGLSurface surface =
-            useProtectedContext ? mProtectedPlaceholderSurface : mPlaceholderSurface;
-    const EGLContext context = useProtectedContext ? mProtectedEGLContext : mEGLContext;
+        (isProtected == GrProtected::kYes) ?
+        mProtectedPlaceholderSurface : mPlaceholderSurface;
+    const EGLContext context = (isProtected == GrProtected::kYes) ?
+        mProtectedEGLContext : mEGLContext;
 
-    if (eglMakeCurrent(mEGLDisplay, surface, surface, context) == EGL_TRUE) {
-        mInProtectedContext = useProtectedContext;
-        // given that we are sharing the same thread between two GrContexts we need to
-        // make sure that the thread state is reset when switching between the two.
-        if (getActiveGrContext()) {
-            getActiveGrContext()->resetContext();
-        }
-    }
+    return eglMakeCurrent(mEGLDisplay, surface, surface, context) == EGL_TRUE;
 }
 
-base::unique_fd SkiaGLRenderEngine::flush() {
-    ATRACE_CALL();
-    if (!gl::GLExtensions::getInstance().hasNativeFenceSync()) {
-        return base::unique_fd();
-    }
-
-    EGLSyncKHR sync = eglCreateSyncKHR(mEGLDisplay, EGL_SYNC_NATIVE_FENCE_ANDROID, nullptr);
-    if (sync == EGL_NO_SYNC_KHR) {
-        ALOGW("failed to create EGL native fence sync: %#x", eglGetError());
-        return base::unique_fd();
-    }
-
-    // native fence fd will not be populated until flush() is done.
-    glFlush();
-
-    // get the fence fd
-    base::unique_fd fenceFd(eglDupNativeFenceFDANDROID(mEGLDisplay, sync));
-    eglDestroySyncKHR(mEGLDisplay, sync);
-    if (fenceFd == EGL_NO_NATIVE_FENCE_FD_ANDROID) {
-        ALOGW("failed to dup EGL native fence sync: %#x", eglGetError());
-    }
-
-    return fenceFd;
-}
-
-void SkiaGLRenderEngine::waitFence(base::borrowed_fd fenceFd) {
+void SkiaGLRenderEngine::waitFence(GrDirectContext*, base::borrowed_fd fenceFd) {
     if (fenceFd.get() >= 0 && !waitGpuFence(fenceFd)) {
         ATRACE_NAME("SkiaGLRenderEngine::waitFence");
         sync_wait(fenceFd.get(), -1);
     }
 }
 
+base::unique_fd SkiaGLRenderEngine::flushAndSubmit(GrDirectContext* grContext) {
+    base::unique_fd drawFence = flush();
+
+    bool requireSync = drawFence.get() < 0;
+    if (requireSync) {
+        ATRACE_BEGIN("Submit(sync=true)");
+    } else {
+        ATRACE_BEGIN("Submit(sync=false)");
+    }
+    bool success = grContext->submit(requireSync);
+    ATRACE_END();
+    if (!success) {
+        ALOGE("Failed to flush RenderEngine commands");
+        // Chances are, something illegal happened (Skia's internal GPU object
+        // doesn't exist, or the context was abandoned).
+        return drawFence;
+    }
+
+    return drawFence;
+}
+
 bool SkiaGLRenderEngine::waitGpuFence(base::borrowed_fd fenceFd) {
     if (!gl::GLExtensions::getInstance().hasNativeFenceSync() ||
         !gl::GLExtensions::getInstance().hasWaitSync()) {
@@ -483,974 +377,29 @@
     return true;
 }
 
-static float toDegrees(uint32_t transform) {
-    switch (transform) {
-        case ui::Transform::ROT_90:
-            return 90.0;
-        case ui::Transform::ROT_180:
-            return 180.0;
-        case ui::Transform::ROT_270:
-            return 270.0;
-        default:
-            return 0.0;
-    }
-}
-
-static SkColorMatrix toSkColorMatrix(const mat4& matrix) {
-    return SkColorMatrix(matrix[0][0], matrix[1][0], matrix[2][0], matrix[3][0], 0, matrix[0][1],
-                         matrix[1][1], matrix[2][1], matrix[3][1], 0, matrix[0][2], matrix[1][2],
-                         matrix[2][2], matrix[3][2], 0, matrix[0][3], matrix[1][3], matrix[2][3],
-                         matrix[3][3], 0);
-}
-
-static bool needsToneMapping(ui::Dataspace sourceDataspace, ui::Dataspace destinationDataspace) {
-    int64_t sourceTransfer = sourceDataspace & HAL_DATASPACE_TRANSFER_MASK;
-    int64_t destTransfer = destinationDataspace & HAL_DATASPACE_TRANSFER_MASK;
-
-    // Treat unsupported dataspaces as srgb
-    if (destTransfer != HAL_DATASPACE_TRANSFER_LINEAR &&
-        destTransfer != HAL_DATASPACE_TRANSFER_HLG &&
-        destTransfer != HAL_DATASPACE_TRANSFER_ST2084) {
-        destTransfer = HAL_DATASPACE_TRANSFER_SRGB;
-    }
-
-    if (sourceTransfer != HAL_DATASPACE_TRANSFER_LINEAR &&
-        sourceTransfer != HAL_DATASPACE_TRANSFER_HLG &&
-        sourceTransfer != HAL_DATASPACE_TRANSFER_ST2084) {
-        sourceTransfer = HAL_DATASPACE_TRANSFER_SRGB;
-    }
-
-    const bool isSourceLinear = sourceTransfer == HAL_DATASPACE_TRANSFER_LINEAR;
-    const bool isSourceSRGB = sourceTransfer == HAL_DATASPACE_TRANSFER_SRGB;
-    const bool isDestLinear = destTransfer == HAL_DATASPACE_TRANSFER_LINEAR;
-    const bool isDestSRGB = destTransfer == HAL_DATASPACE_TRANSFER_SRGB;
-
-    return !(isSourceLinear && isDestSRGB) && !(isSourceSRGB && isDestLinear) &&
-            sourceTransfer != destTransfer;
-}
-
-void SkiaGLRenderEngine::mapExternalTextureBuffer(const sp<GraphicBuffer>& buffer,
-                                                  bool isRenderable) {
-    // Only run this if RE is running on its own thread. This way the access to GL
-    // operations is guaranteed to be happening on the same thread.
-    if (mRenderEngineType != RenderEngineType::SKIA_GL_THREADED) {
-        return;
-    }
-    // We currently don't attempt to map a buffer if the buffer contains protected content
-    // because GPU resources for protected buffers is much more limited.
-    const bool isProtectedBuffer = buffer->getUsage() & GRALLOC_USAGE_PROTECTED;
-    if (isProtectedBuffer) {
-        return;
-    }
+base::unique_fd SkiaGLRenderEngine::flush() {
     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).  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;
-
-    std::lock_guard<std::mutex> lock(mRenderingMutex);
-    mGraphicBufferExternalRefs[buffer->getId()]++;
-
-    if (const auto& iter = cache.find(buffer->getId()); iter == cache.end()) {
-        std::shared_ptr<AutoBackendTexture::LocalRef> imageTextureRef =
-                std::make_shared<AutoBackendTexture::LocalRef>(grContext,
-                                                               buffer->toAHardwareBuffer(),
-                                                               isRenderable, mTextureCleanupMgr);
-        cache.insert({buffer->getId(), imageTextureRef});
-    }
-}
-
-void SkiaGLRenderEngine::unmapExternalTextureBuffer(const sp<GraphicBuffer>& buffer) {
-    ATRACE_CALL();
-    std::lock_guard<std::mutex> lock(mRenderingMutex);
-    if (const auto& iter = mGraphicBufferExternalRefs.find(buffer->getId());
-        iter != mGraphicBufferExternalRefs.end()) {
-        if (iter->second == 0) {
-            ALOGW("Attempted to unmap GraphicBuffer <id: %" PRId64
-                  "> from RenderEngine texture, but the "
-                  "ref count was already zero!",
-                  buffer->getId());
-            mGraphicBufferExternalRefs.erase(buffer->getId());
-            return;
-        }
-
-        iter->second--;
-
-        // Swap contexts if needed prior to deleting this buffer
-        // See Issue 1 of
-        // https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_protected_content.txt: even
-        // when a protected context and an unprotected context are part of the same share group,
-        // protected surfaces may not be accessed by an unprotected context, implying that protected
-        // surfaces may only be freed when a protected context is active.
-        const bool inProtected = mInProtectedContext;
-        useProtectedContext(buffer->getUsage() & GRALLOC_USAGE_PROTECTED);
-
-        if (iter->second == 0) {
-            mTextureCache.erase(buffer->getId());
-            mGraphicBufferExternalRefs.erase(buffer->getId());
-        }
-
-        // Swap back to the previous context so that cached values of isProtected in SurfaceFlinger
-        // are up-to-date.
-        if (inProtected != mInProtectedContext) {
-            useProtectedContext(inProtected);
-        }
-    }
-}
-
-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(
-        const RuntimeEffectShaderParameters& parameters) {
-    // The given surface will be stretched by HWUI via matrix transformation
-    // which gets similar results for most surfaces
-    // Determine later on if we need to leverage the stertch shader within
-    // surface flinger
-    const auto& stretchEffect = parameters.layer.stretchEffect;
-    auto shader = parameters.shader;
-    if (stretchEffect.hasEffect()) {
-        const auto targetBuffer = parameters.layer.source.buffer.buffer;
-        const auto graphicBuffer = targetBuffer ? targetBuffer->getBuffer() : nullptr;
-        if (graphicBuffer && parameters.shader) {
-            shader = mStretchShaderFactory.createSkShader(shader, stretchEffect);
-        }
+    if (!gl::GLExtensions::getInstance().hasNativeFenceSync()) {
+        return base::unique_fd();
     }
 
-    if (parameters.requiresLinearEffect) {
-        const ui::Dataspace inputDataspace = mUseColorManagement ? parameters.layer.sourceDataspace
-                                                                 : ui::Dataspace::V0_SRGB_LINEAR;
-        const ui::Dataspace outputDataspace = mUseColorManagement
-                ? parameters.display.outputDataspace
-                : ui::Dataspace::V0_SRGB_LINEAR;
-
-        auto effect =
-                shaders::LinearEffect{.inputDataspace = inputDataspace,
-                                      .outputDataspace = outputDataspace,
-                                      .undoPremultipliedAlpha = parameters.undoPremultipliedAlpha};
-
-        auto effectIter = mRuntimeEffects.find(effect);
-        sk_sp<SkRuntimeEffect> runtimeEffect = nullptr;
-        if (effectIter == mRuntimeEffects.end()) {
-            runtimeEffect = buildRuntimeEffect(effect);
-            mRuntimeEffects.insert({effect, runtimeEffect});
-        } else {
-            runtimeEffect = effectIter->second;
-        }
-        mat4 colorTransform = parameters.layer.colorTransform;
-
-        colorTransform *=
-                mat4::scale(vec4(parameters.layerDimmingRatio, parameters.layerDimmingRatio,
-                                 parameters.layerDimmingRatio, 1.f));
-        const auto targetBuffer = parameters.layer.source.buffer.buffer;
-        const auto graphicBuffer = targetBuffer ? targetBuffer->getBuffer() : nullptr;
-        const auto hardwareBuffer = graphicBuffer ? graphicBuffer->toAHardwareBuffer() : nullptr;
-        return createLinearEffectShader(parameters.shader, effect, runtimeEffect, colorTransform,
-                                        parameters.display.maxLuminance,
-                                        parameters.display.currentLuminanceNits,
-                                        parameters.layer.source.buffer.maxLuminanceNits,
-                                        hardwareBuffer, parameters.display.renderIntent);
-    }
-    return parameters.shader;
-}
-
-void SkiaGLRenderEngine::initCanvas(SkCanvas* canvas, const DisplaySettings& display) {
-    if (CC_UNLIKELY(mCapture->isCaptureRunning())) {
-        // Record display settings when capture is running.
-        std::stringstream displaySettings;
-        PrintTo(display, &displaySettings);
-        // Store the DisplaySettings in additional information.
-        canvas->drawAnnotation(SkRect::MakeEmpty(), "DisplaySettings",
-                               SkData::MakeWithCString(displaySettings.str().c_str()));
+    EGLSyncKHR sync = eglCreateSyncKHR(mEGLDisplay, EGL_SYNC_NATIVE_FENCE_ANDROID, nullptr);
+    if (sync == EGL_NO_SYNC_KHR) {
+        ALOGW("failed to create EGL native fence sync: %#x", eglGetError());
+        return base::unique_fd();
     }
 
-    // Before doing any drawing, let's make sure that we'll start at the origin of the display.
-    // Some displays don't start at 0,0 for example when we're mirroring the screen. Also, virtual
-    // displays might have different scaling when compared to the physical screen.
+    // native fence fd will not be populated until flush() is done.
+    glFlush();
 
-    canvas->clipRect(getSkRect(display.physicalDisplay));
-    canvas->translate(display.physicalDisplay.left, display.physicalDisplay.top);
-
-    const auto clipWidth = display.clip.width();
-    const auto clipHeight = display.clip.height();
-    auto rotatedClipWidth = clipWidth;
-    auto rotatedClipHeight = clipHeight;
-    // Scale is contingent on the rotation result.
-    if (display.orientation & ui::Transform::ROT_90) {
-        std::swap(rotatedClipWidth, rotatedClipHeight);
-    }
-    const auto scaleX = static_cast<SkScalar>(display.physicalDisplay.width()) /
-            static_cast<SkScalar>(rotatedClipWidth);
-    const auto scaleY = static_cast<SkScalar>(display.physicalDisplay.height()) /
-            static_cast<SkScalar>(rotatedClipHeight);
-    canvas->scale(scaleX, scaleY);
-
-    // Canvas rotation is done by centering the clip window at the origin, rotating, translating
-    // back so that the top left corner of the clip is at (0, 0).
-    canvas->translate(rotatedClipWidth / 2, rotatedClipHeight / 2);
-    canvas->rotate(toDegrees(display.orientation));
-    canvas->translate(-clipWidth / 2, -clipHeight / 2);
-    canvas->translate(-display.clip.left, -display.clip.top);
-}
-
-class AutoSaveRestore {
-public:
-    AutoSaveRestore(SkCanvas* canvas) : mCanvas(canvas) { mSaveCount = canvas->save(); }
-    ~AutoSaveRestore() { restore(); }
-    void replace(SkCanvas* canvas) {
-        mCanvas = canvas;
-        mSaveCount = canvas->save();
-    }
-    void restore() {
-        if (mCanvas) {
-            mCanvas->restoreToCount(mSaveCount);
-            mCanvas = nullptr;
-        }
+    // get the fence fd
+    base::unique_fd fenceFd(eglDupNativeFenceFDANDROID(mEGLDisplay, sync));
+    eglDestroySyncKHR(mEGLDisplay, sync);
+    if (fenceFd == EGL_NO_NATIVE_FENCE_FD_ANDROID) {
+        ALOGW("failed to dup EGL native fence sync: %#x", eglGetError());
     }
 
-private:
-    SkCanvas* mCanvas;
-    int mSaveCount;
-};
-
-static SkRRect getBlurRRect(const BlurRegion& region) {
-    const auto rect = SkRect::MakeLTRB(region.left, region.top, region.right, region.bottom);
-    const SkVector radii[4] = {SkVector::Make(region.cornerRadiusTL, region.cornerRadiusTL),
-                               SkVector::Make(region.cornerRadiusTR, region.cornerRadiusTR),
-                               SkVector::Make(region.cornerRadiusBR, region.cornerRadiusBR),
-                               SkVector::Make(region.cornerRadiusBL, region.cornerRadiusBL)};
-    SkRRect roundedRect;
-    roundedRect.setRectRadii(rect, radii);
-    return roundedRect;
-}
-
-// Arbitrary default margin which should be close enough to zero.
-constexpr float kDefaultMargin = 0.0001f;
-static bool equalsWithinMargin(float expected, float value, float margin = kDefaultMargin) {
-    LOG_ALWAYS_FATAL_IF(margin < 0.f, "Margin is negative!");
-    return std::abs(expected - value) < margin;
-}
-
-namespace {
-template <typename T>
-void logSettings(const T& t) {
-    std::stringstream stream;
-    PrintTo(t, &stream);
-    auto string = stream.str();
-    size_t pos = 0;
-    // Perfetto ignores \n, so split up manually into separate ALOGD statements.
-    const size_t size = string.size();
-    while (pos < size) {
-        const size_t end = std::min(string.find("\n", pos), size);
-        ALOGD("%s", string.substr(pos, end - pos).c_str());
-        pos = end + 1;
-    }
-}
-} // namespace
-
-void SkiaGLRenderEngine::drawLayersInternal(
-        const std::shared_ptr<std::promise<RenderEngineResult>>&& resultPromise,
-        const DisplaySettings& display, const std::vector<LayerSettings>& layers,
-        const std::shared_ptr<ExternalTexture>& buffer, const bool /*useFramebufferCache*/,
-        base::unique_fd&& bufferFence) {
-    ATRACE_NAME("SkiaGL::drawLayersInternal");
-
-    std::lock_guard<std::mutex> lock(mRenderingMutex);
-    if (buffer == nullptr) {
-        ALOGE("No output buffer provided. Aborting GPU composition.");
-        resultPromise->set_value({BAD_VALUE, base::unique_fd()});
-        return;
-    }
-
-    validateOutputBufferUsage(buffer->getBuffer());
-
-    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;
-    } else {
-        surfaceTextureRef =
-                std::make_shared<AutoBackendTexture::LocalRef>(grContext,
-                                                               buffer->getBuffer()
-                                                                       ->toAHardwareBuffer(),
-                                                               true, mTextureCleanupMgr);
-    }
-
-    // wait on the buffer to be ready to use prior to using it
-    waitFence(bufferFence);
-
-    const ui::Dataspace dstDataspace =
-            mUseColorManagement ? display.outputDataspace : ui::Dataspace::V0_SRGB_LINEAR;
-    sk_sp<SkSurface> dstSurface = surfaceTextureRef->getOrCreateSurface(dstDataspace, grContext);
-
-    SkCanvas* dstCanvas = mCapture->tryCapture(dstSurface.get());
-    if (dstCanvas == nullptr) {
-        ALOGE("Cannot acquire canvas from Skia.");
-        resultPromise->set_value({BAD_VALUE, base::unique_fd()});
-        return;
-    }
-
-    // setup color filter if necessary
-    sk_sp<SkColorFilter> displayColorTransform;
-    if (display.colorTransform != mat4() && !display.deviceHandlesColorTransform) {
-        displayColorTransform = SkColorFilters::Matrix(toSkColorMatrix(display.colorTransform));
-    }
-    const bool ctModifiesAlpha =
-            displayColorTransform && !displayColorTransform->isAlphaUnchanged();
-
-    // Find the max layer white point to determine the max luminance of the scene...
-    const float maxLayerWhitePoint = std::transform_reduce(
-            layers.cbegin(), layers.cend(), 0.f,
-            [](float left, float right) { return std::max(left, right); },
-            [&](const auto& l) { return l.whitePointNits; });
-
-    // ...and compute the dimming ratio if dimming is requested
-    const float displayDimmingRatio = display.targetLuminanceNits > 0.f &&
-                    maxLayerWhitePoint > 0.f && display.targetLuminanceNits > maxLayerWhitePoint
-            ? maxLayerWhitePoint / display.targetLuminanceNits
-            : 1.f;
-
-    // Find if any layers have requested blur, we'll use that info to decide when to render to an
-    // offscreen buffer and when to render to the native buffer.
-    sk_sp<SkSurface> activeSurface(dstSurface);
-    SkCanvas* canvas = dstCanvas;
-    SkiaCapture::OffscreenState offscreenCaptureState;
-    const LayerSettings* blurCompositionLayer = nullptr;
-    if (mBlurFilter) {
-        bool requiresCompositionLayer = false;
-        for (const auto& layer : layers) {
-            // if the layer doesn't have blur or it is not visible then continue
-            if (!layerHasBlur(layer, ctModifiesAlpha)) {
-                continue;
-            }
-            if (layer.backgroundBlurRadius > 0 &&
-                layer.backgroundBlurRadius < mBlurFilter->getMaxCrossFadeRadius()) {
-                requiresCompositionLayer = true;
-            }
-            for (auto region : layer.blurRegions) {
-                if (region.blurRadius < mBlurFilter->getMaxCrossFadeRadius()) {
-                    requiresCompositionLayer = true;
-                }
-            }
-            if (requiresCompositionLayer) {
-                activeSurface = dstSurface->makeSurface(dstSurface->imageInfo());
-                canvas = mCapture->tryOffscreenCapture(activeSurface.get(), &offscreenCaptureState);
-                blurCompositionLayer = &layer;
-                break;
-            }
-        }
-    }
-
-    AutoSaveRestore surfaceAutoSaveRestore(canvas);
-    // Clear the entire canvas with a transparent black to prevent ghost images.
-    canvas->clear(SK_ColorTRANSPARENT);
-    initCanvas(canvas, display);
-
-    if (kPrintLayerSettings) {
-        logSettings(display);
-    }
-    for (const auto& layer : layers) {
-        ATRACE_FORMAT("DrawLayer: %s", layer.name.c_str());
-
-        if (kPrintLayerSettings) {
-            logSettings(layer);
-        }
-
-        sk_sp<SkImage> blurInput;
-        if (blurCompositionLayer == &layer) {
-            LOG_ALWAYS_FATAL_IF(activeSurface == dstSurface);
-            LOG_ALWAYS_FATAL_IF(canvas == dstCanvas);
-
-            // save a snapshot of the activeSurface to use as input to the blur shaders
-            blurInput = activeSurface->makeImageSnapshot();
-
-            // blit the offscreen framebuffer into the destination AHB, but only
-            // if there are blur regions. backgroundBlurRadius blurs the entire
-            // image below, so it can skip this step.
-            if (layer.blurRegions.size()) {
-                SkPaint paint;
-                paint.setBlendMode(SkBlendMode::kSrc);
-                if (CC_UNLIKELY(mCapture->isCaptureRunning())) {
-                    uint64_t id = mCapture->endOffscreenCapture(&offscreenCaptureState);
-                    dstCanvas->drawAnnotation(SkRect::Make(dstCanvas->imageInfo().dimensions()),
-                                              String8::format("SurfaceID|%" PRId64, id).c_str(),
-                                              nullptr);
-                    dstCanvas->drawImage(blurInput, 0, 0, SkSamplingOptions(), &paint);
-                } else {
-                    activeSurface->draw(dstCanvas, 0, 0, SkSamplingOptions(), &paint);
-                }
-            }
-
-            // assign dstCanvas to canvas and ensure that the canvas state is up to date
-            canvas = dstCanvas;
-            surfaceAutoSaveRestore.replace(canvas);
-            initCanvas(canvas, display);
-
-            LOG_ALWAYS_FATAL_IF(activeSurface->getCanvas()->getSaveCount() !=
-                                dstSurface->getCanvas()->getSaveCount());
-            LOG_ALWAYS_FATAL_IF(activeSurface->getCanvas()->getTotalMatrix() !=
-                                dstSurface->getCanvas()->getTotalMatrix());
-
-            // assign dstSurface to activeSurface
-            activeSurface = dstSurface;
-        }
-
-        SkAutoCanvasRestore layerAutoSaveRestore(canvas, true);
-        if (CC_UNLIKELY(mCapture->isCaptureRunning())) {
-            // Record the name of the layer if the capture is running.
-            std::stringstream layerSettings;
-            PrintTo(layer, &layerSettings);
-            // Store the LayerSettings in additional information.
-            canvas->drawAnnotation(SkRect::MakeEmpty(), layer.name.c_str(),
-                                   SkData::MakeWithCString(layerSettings.str().c_str()));
-        }
-        // Layers have a local transform that should be applied to them
-        canvas->concat(getSkM44(layer.geometry.positionTransform).asM33());
-
-        const auto [bounds, roundRectClip] =
-                getBoundsAndClip(layer.geometry.boundaries, layer.geometry.roundedCornersCrop,
-                                 layer.geometry.roundedCornersRadius);
-        if (mBlurFilter && layerHasBlur(layer, ctModifiesAlpha)) {
-            std::unordered_map<uint32_t, sk_sp<SkImage>> cachedBlurs;
-
-            // if multiple layers have blur, then we need to take a snapshot now because
-            // only the lowest layer will have blurImage populated earlier
-            if (!blurInput) {
-                blurInput = activeSurface->makeImageSnapshot();
-            }
-            // rect to be blurred in the coordinate space of blurInput
-            const auto blurRect = canvas->getTotalMatrix().mapRect(bounds.rect());
-
-            // if the clip needs to be applied then apply it now and make sure
-            // it is restored before we attempt to draw any shadows.
-            SkAutoCanvasRestore acr(canvas, true);
-            if (!roundRectClip.isEmpty()) {
-                canvas->clipRRect(roundRectClip, true);
-            }
-
-            // TODO(b/182216890): Filter out empty layers earlier
-            if (blurRect.width() > 0 && blurRect.height() > 0) {
-                if (layer.backgroundBlurRadius > 0) {
-                    ATRACE_NAME("BackgroundBlur");
-                    auto blurredImage = mBlurFilter->generate(grContext, layer.backgroundBlurRadius,
-                                                              blurInput, blurRect);
-
-                    cachedBlurs[layer.backgroundBlurRadius] = blurredImage;
-
-                    mBlurFilter->drawBlurRegion(canvas, bounds, layer.backgroundBlurRadius, 1.0f,
-                                                blurRect, blurredImage, blurInput);
-                }
-
-                canvas->concat(getSkM44(layer.blurRegionTransform).asM33());
-                for (auto region : layer.blurRegions) {
-                    if (cachedBlurs[region.blurRadius] == nullptr) {
-                        ATRACE_NAME("BlurRegion");
-                        cachedBlurs[region.blurRadius] =
-                                mBlurFilter->generate(grContext, region.blurRadius, blurInput,
-                                                      blurRect);
-                    }
-
-                    mBlurFilter->drawBlurRegion(canvas, getBlurRRect(region), region.blurRadius,
-                                                region.alpha, blurRect,
-                                                cachedBlurs[region.blurRadius], blurInput);
-                }
-            }
-        }
-
-        if (layer.shadow.length > 0) {
-            // This would require a new parameter/flag to SkShadowUtils::DrawShadow
-            LOG_ALWAYS_FATAL_IF(layer.disableBlending, "Cannot disableBlending with a shadow");
-
-            SkRRect shadowBounds, shadowClip;
-            if (layer.geometry.boundaries == layer.shadow.boundaries) {
-                shadowBounds = bounds;
-                shadowClip = roundRectClip;
-            } else {
-                std::tie(shadowBounds, shadowClip) =
-                        getBoundsAndClip(layer.shadow.boundaries, layer.geometry.roundedCornersCrop,
-                                         layer.geometry.roundedCornersRadius);
-            }
-
-            // Technically, if bounds is a rect and roundRectClip is not empty,
-            // it means that the bounds and roundedCornersCrop were different
-            // enough that we should intersect them to find the proper shadow.
-            // In practice, this often happens when the two rectangles appear to
-            // not match due to rounding errors. Draw the rounded version, which
-            // looks more like the intent.
-            const auto& rrect =
-                    shadowBounds.isRect() && !shadowClip.isEmpty() ? shadowClip : shadowBounds;
-            drawShadow(canvas, rrect, layer.shadow);
-        }
-
-        const float layerDimmingRatio = layer.whitePointNits <= 0.f
-                ? displayDimmingRatio
-                : (layer.whitePointNits / maxLayerWhitePoint) * displayDimmingRatio;
-
-        const bool dimInLinearSpace = display.dimmingStage !=
-                aidl::android::hardware::graphics::composer3::DimmingStage::GAMMA_OETF;
-
-        const bool requiresLinearEffect = layer.colorTransform != mat4() ||
-                (mUseColorManagement &&
-                 needsToneMapping(layer.sourceDataspace, display.outputDataspace)) ||
-                (dimInLinearSpace && !equalsWithinMargin(1.f, layerDimmingRatio));
-
-        // quick abort from drawing the remaining portion of the layer
-        if (layer.skipContentDraw ||
-            (layer.alpha == 0 && !requiresLinearEffect && !layer.disableBlending &&
-             (!displayColorTransform || displayColorTransform->isAlphaUnchanged()))) {
-            continue;
-        }
-
-        // If we need to map to linear space or color management is disabled, then mark the source
-        // image with the same colorspace as the destination surface so that Skia's color
-        // management is a no-op.
-        const ui::Dataspace layerDataspace = (!mUseColorManagement || requiresLinearEffect)
-                ? dstDataspace
-                : layer.sourceDataspace;
-
-        SkPaint paint;
-        if (layer.source.buffer.buffer) {
-            ATRACE_NAME("DrawImage");
-            validateInputBufferUsage(layer.source.buffer.buffer->getBuffer());
-            const auto& item = layer.source.buffer;
-            std::shared_ptr<AutoBackendTexture::LocalRef> imageTextureRef = nullptr;
-
-            if (const auto& iter = cache.find(item.buffer->getBuffer()->getId());
-                iter != cache.end()) {
-                imageTextureRef = iter->second;
-            } else {
-                // If we didn't find the image in the cache, then create a local ref but don't cache
-                // it. If we're using skia, we're guaranteed to run on a dedicated GPU thread so if
-                // we didn't find anything in the cache then we intentionally did not cache this
-                // buffer's resources.
-                imageTextureRef = std::make_shared<
-                        AutoBackendTexture::LocalRef>(grContext,
-                                                      item.buffer->getBuffer()->toAHardwareBuffer(),
-                                                      false, mTextureCleanupMgr);
-            }
-
-            // if the layer's buffer has a fence, then we must must respect the fence prior to using
-            // the buffer.
-            if (layer.source.buffer.fence != nullptr) {
-                waitFence(layer.source.buffer.fence->get());
-            }
-
-            // isOpaque means we need to ignore the alpha in the image,
-            // replacing it with the alpha specified by the LayerSettings. See
-            // https://developer.android.com/reference/android/view/SurfaceControl.Builder#setOpaque(boolean)
-            // The proper way to do this is to use an SkColorType that ignores
-            // alpha, like kRGB_888x_SkColorType, and that is used if the
-            // incoming image is kRGBA_8888_SkColorType. However, the incoming
-            // image may be kRGBA_F16_SkColorType, for which there is no RGBX
-            // SkColorType, or kRGBA_1010102_SkColorType, for which we have
-            // kRGB_101010x_SkColorType, but it is not yet supported as a source
-            // on the GPU. (Adding both is tracked in skbug.com/12048.) In the
-            // meantime, we'll use a workaround that works unless we need to do
-            // any color conversion. The workaround requires that we pretend the
-            // image is already premultiplied, so that we do not premultiply it
-            // before applying SkBlendMode::kPlus.
-            const bool useIsOpaqueWorkaround = item.isOpaque &&
-                    (imageTextureRef->colorType() == kRGBA_1010102_SkColorType ||
-                     imageTextureRef->colorType() == kRGBA_F16_SkColorType);
-            const auto alphaType = useIsOpaqueWorkaround ? kPremul_SkAlphaType
-                    : item.isOpaque                      ? kOpaque_SkAlphaType
-                    : item.usePremultipliedAlpha         ? kPremul_SkAlphaType
-                                                         : kUnpremul_SkAlphaType;
-            sk_sp<SkImage> image = imageTextureRef->makeImage(layerDataspace, alphaType, grContext);
-
-            auto texMatrix = getSkM44(item.textureTransform).asM33();
-            // textureTansform was intended to be passed directly into a shader, so when
-            // building the total matrix with the textureTransform we need to first
-            // normalize it, then apply the textureTransform, then scale back up.
-            texMatrix.preScale(1.0f / bounds.width(), 1.0f / bounds.height());
-            texMatrix.postScale(image->width(), image->height());
-
-            SkMatrix matrix;
-            if (!texMatrix.invert(&matrix)) {
-                matrix = texMatrix;
-            }
-            // The shader does not respect the translation, so we add it to the texture
-            // transform for the SkImage. This will make sure that the correct layer contents
-            // are drawn in the correct part of the screen.
-            matrix.postTranslate(bounds.rect().fLeft, bounds.rect().fTop);
-
-            sk_sp<SkShader> shader;
-
-            if (layer.source.buffer.useTextureFiltering) {
-                shader = image->makeShader(SkTileMode::kClamp, SkTileMode::kClamp,
-                                           SkSamplingOptions(
-                                                   {SkFilterMode::kLinear, SkMipmapMode::kNone}),
-                                           &matrix);
-            } else {
-                shader = image->makeShader(SkSamplingOptions(), matrix);
-            }
-
-            if (useIsOpaqueWorkaround) {
-                shader = SkShaders::Blend(SkBlendMode::kPlus, shader,
-                                          SkShaders::Color(SkColors::kBlack,
-                                                           toSkColorSpace(layerDataspace)));
-            }
-
-            paint.setShader(createRuntimeEffectShader(
-                    RuntimeEffectShaderParameters{.shader = shader,
-                                                  .layer = layer,
-                                                  .display = display,
-                                                  .undoPremultipliedAlpha = !item.isOpaque &&
-                                                          item.usePremultipliedAlpha,
-                                                  .requiresLinearEffect = requiresLinearEffect,
-                                                  .layerDimmingRatio = dimInLinearSpace
-                                                          ? layerDimmingRatio
-                                                          : 1.f}));
-
-            // Turn on dithering when dimming beyond this (arbitrary) threshold...
-            static constexpr float kDimmingThreshold = 0.2f;
-            // ...or we're rendering an HDR layer down to an 8-bit target
-            // Most HDR standards require at least 10-bits of color depth for source content, so we
-            // can just extract the transfer function rather than dig into precise gralloc layout.
-            // Furthermore, we can assume that the only 8-bit target we support is RGBA8888.
-            const bool requiresDownsample = isHdrDataspace(layer.sourceDataspace) &&
-                    buffer->getPixelFormat() == PIXEL_FORMAT_RGBA_8888;
-            if (layerDimmingRatio <= kDimmingThreshold || requiresDownsample) {
-                paint.setDither(true);
-            }
-            paint.setAlphaf(layer.alpha);
-
-            if (imageTextureRef->colorType() == kAlpha_8_SkColorType) {
-                LOG_ALWAYS_FATAL_IF(layer.disableBlending, "Cannot disableBlending with A8");
-
-                // SysUI creates the alpha layer as a coverage layer, which is
-                // appropriate for the DPU. Use a color matrix to convert it to
-                // a mask.
-                // TODO (b/219525258): Handle input as a mask.
-                //
-                // The color matrix will convert A8 pixels with no alpha to
-                // black, as described by this vector. If the display handles
-                // the color transform, we need to invert it to find the color
-                // that will result in black after the DPU applies the transform.
-                SkV4 black{0.0f, 0.0f, 0.0f, 1.0f}; // r, g, b, a
-                if (display.colorTransform != mat4() && display.deviceHandlesColorTransform) {
-                    SkM44 colorSpaceMatrix = getSkM44(display.colorTransform);
-                    if (colorSpaceMatrix.invert(&colorSpaceMatrix)) {
-                        black = colorSpaceMatrix * black;
-                    } else {
-                        // We'll just have to use 0,0,0 as black, which should
-                        // be close to correct.
-                        ALOGI("Could not invert colorTransform!");
-                    }
-                }
-                SkColorMatrix colorMatrix(0, 0, 0, 0, black[0],
-                                          0, 0, 0, 0, black[1],
-                                          0, 0, 0, 0, black[2],
-                                          0, 0, 0, -1, 1);
-                if (display.colorTransform != mat4() && !display.deviceHandlesColorTransform) {
-                    // On the other hand, if the device doesn't handle it, we
-                    // have to apply it ourselves.
-                    colorMatrix.postConcat(toSkColorMatrix(display.colorTransform));
-                }
-                paint.setColorFilter(SkColorFilters::Matrix(colorMatrix));
-            }
-        } else {
-            ATRACE_NAME("DrawColor");
-            const auto color = layer.source.solidColor;
-            sk_sp<SkShader> shader = SkShaders::Color(SkColor4f{.fR = color.r,
-                                                                .fG = color.g,
-                                                                .fB = color.b,
-                                                                .fA = layer.alpha},
-                                                      toSkColorSpace(layerDataspace));
-            paint.setShader(createRuntimeEffectShader(
-                    RuntimeEffectShaderParameters{.shader = shader,
-                                                  .layer = layer,
-                                                  .display = display,
-                                                  .undoPremultipliedAlpha = false,
-                                                  .requiresLinearEffect = requiresLinearEffect,
-                                                  .layerDimmingRatio = layerDimmingRatio}));
-        }
-
-        if (layer.disableBlending) {
-            paint.setBlendMode(SkBlendMode::kSrc);
-        }
-
-        // An A8 buffer will already have the proper color filter attached to
-        // its paint, including the displayColorTransform as needed.
-        if (!paint.getColorFilter()) {
-            if (!dimInLinearSpace && !equalsWithinMargin(1.0, layerDimmingRatio)) {
-                // If we don't dim in linear space, then when we gamma correct the dimming ratio we
-                // can assume a gamma 2.2 transfer function.
-                static constexpr float kInverseGamma22 = 1.f / 2.2f;
-                const auto gammaCorrectedDimmingRatio =
-                        std::pow(layerDimmingRatio, kInverseGamma22);
-                auto dimmingMatrix =
-                        mat4::scale(vec4(gammaCorrectedDimmingRatio, gammaCorrectedDimmingRatio,
-                                         gammaCorrectedDimmingRatio, 1.f));
-
-                const auto colorFilter =
-                        SkColorFilters::Matrix(toSkColorMatrix(std::move(dimmingMatrix)));
-                paint.setColorFilter(displayColorTransform
-                                             ? displayColorTransform->makeComposed(colorFilter)
-                                             : colorFilter);
-            } else {
-                paint.setColorFilter(displayColorTransform);
-            }
-        }
-
-        if (!roundRectClip.isEmpty()) {
-            canvas->clipRRect(roundRectClip, true);
-        }
-
-        if (!bounds.isRect()) {
-            paint.setAntiAlias(true);
-            canvas->drawRRect(bounds, paint);
-        } else {
-            canvas->drawRect(bounds.rect(), paint);
-        }
-        if (kFlushAfterEveryLayer) {
-            ATRACE_NAME("flush surface");
-            activeSurface->flush();
-        }
-    }
-    for (const auto& borderRenderInfo : display.borderInfoList) {
-        SkPaint p;
-        p.setColor(SkColor4f{borderRenderInfo.color.r, borderRenderInfo.color.g,
-                             borderRenderInfo.color.b, borderRenderInfo.color.a});
-        p.setAntiAlias(true);
-        p.setStyle(SkPaint::kStroke_Style);
-        p.setStrokeWidth(borderRenderInfo.width);
-        SkRegion sk_region;
-        SkPath path;
-
-        // Construct a final SkRegion using Regions
-        for (const auto& r : borderRenderInfo.combinedRegion) {
-            sk_region.op({r.left, r.top, r.right, r.bottom}, SkRegion::kUnion_Op);
-        }
-
-        sk_region.getBoundaryPath(&path);
-        canvas->drawPath(path, p);
-        path.close();
-    }
-
-    surfaceAutoSaveRestore.restore();
-    mCapture->endCapture();
-    {
-        ATRACE_NAME("flush surface");
-        LOG_ALWAYS_FATAL_IF(activeSurface != dstSurface);
-        activeSurface->flush();
-    }
-
-    base::unique_fd drawFence = flush();
-
-    // If flush failed or we don't support native fences, we need to force the
-    // gl command stream to be executed.
-    bool requireSync = drawFence.get() < 0;
-    if (requireSync) {
-        ATRACE_BEGIN("Submit(sync=true)");
-    } else {
-        ATRACE_BEGIN("Submit(sync=false)");
-    }
-    bool success = grContext->submit(requireSync);
-    ATRACE_END();
-    if (!success) {
-        ALOGE("Failed to flush RenderEngine commands");
-        // Chances are, something illegal happened (either the caller passed
-        // us bad parameters, or we messed up our shader generation).
-        resultPromise->set_value({INVALID_OPERATION, std::move(drawFence)});
-        return;
-    }
-
-    // checkErrors();
-    resultPromise->set_value({NO_ERROR, std::move(drawFence)});
-    return;
-}
-
-inline SkRect SkiaGLRenderEngine::getSkRect(const FloatRect& rect) {
-    return SkRect::MakeLTRB(rect.left, rect.top, rect.right, rect.bottom);
-}
-
-inline SkRect SkiaGLRenderEngine::getSkRect(const Rect& rect) {
-    return SkRect::MakeLTRB(rect.left, rect.top, rect.right, rect.bottom);
-}
-
-/**
- *  Verifies that common, simple bounds + clip combinations can be converted into
- *  a single RRect draw call returning true if possible. If true the radii parameter
- *  will be filled with the correct radii values that combined with bounds param will
- *  produce the insected roundRect. If false, the returned state of the radii param is undefined.
- */
-static bool intersectionIsRoundRect(const SkRect& bounds, const SkRect& crop,
-                                    const SkRect& insetCrop, const vec2& cornerRadius,
-                                    SkVector radii[4]) {
-    const bool leftEqual = bounds.fLeft == crop.fLeft;
-    const bool topEqual = bounds.fTop == crop.fTop;
-    const bool rightEqual = bounds.fRight == crop.fRight;
-    const bool bottomEqual = bounds.fBottom == crop.fBottom;
-
-    // In the event that the corners of the bounds only partially align with the crop we
-    // need to ensure that the resulting shape can still be represented as a round rect.
-    // In particular the round rect implementation will scale the value of all corner radii
-    // if the sum of the radius along any edge is greater than the length of that edge.
-    // See https://www.w3.org/TR/css-backgrounds-3/#corner-overlap
-    const bool requiredWidth = bounds.width() > (cornerRadius.x * 2);
-    const bool requiredHeight = bounds.height() > (cornerRadius.y * 2);
-    if (!requiredWidth || !requiredHeight) {
-        return false;
-    }
-
-    // Check each cropped corner to ensure that it exactly matches the crop or its corner is
-    // contained within the cropped shape and does not need rounded.
-    // compute the UpperLeft corner radius
-    if (leftEqual && topEqual) {
-        radii[0].set(cornerRadius.x, cornerRadius.y);
-    } else if ((leftEqual && bounds.fTop >= insetCrop.fTop) ||
-               (topEqual && bounds.fLeft >= insetCrop.fLeft)) {
-        radii[0].set(0, 0);
-    } else {
-        return false;
-    }
-    // compute the UpperRight corner radius
-    if (rightEqual && topEqual) {
-        radii[1].set(cornerRadius.x, cornerRadius.y);
-    } else if ((rightEqual && bounds.fTop >= insetCrop.fTop) ||
-               (topEqual && bounds.fRight <= insetCrop.fRight)) {
-        radii[1].set(0, 0);
-    } else {
-        return false;
-    }
-    // compute the BottomRight corner radius
-    if (rightEqual && bottomEqual) {
-        radii[2].set(cornerRadius.x, cornerRadius.y);
-    } else if ((rightEqual && bounds.fBottom <= insetCrop.fBottom) ||
-               (bottomEqual && bounds.fRight <= insetCrop.fRight)) {
-        radii[2].set(0, 0);
-    } else {
-        return false;
-    }
-    // compute the BottomLeft corner radius
-    if (leftEqual && bottomEqual) {
-        radii[3].set(cornerRadius.x, cornerRadius.y);
-    } else if ((leftEqual && bounds.fBottom <= insetCrop.fBottom) ||
-               (bottomEqual && bounds.fLeft >= insetCrop.fLeft)) {
-        radii[3].set(0, 0);
-    } else {
-        return false;
-    }
-
-    return true;
-}
-
-inline std::pair<SkRRect, SkRRect> SkiaGLRenderEngine::getBoundsAndClip(const FloatRect& boundsRect,
-                                                                        const FloatRect& cropRect,
-                                                                        const vec2& cornerRadius) {
-    const SkRect bounds = getSkRect(boundsRect);
-    const SkRect crop = getSkRect(cropRect);
-
-    SkRRect clip;
-    if (cornerRadius.x > 0 && cornerRadius.y > 0) {
-        // it the crop and the bounds are equivalent or there is no crop then we don't need a clip
-        if (bounds == crop || crop.isEmpty()) {
-            return {SkRRect::MakeRectXY(bounds, cornerRadius.x, cornerRadius.y), clip};
-        }
-
-        // This makes an effort to speed up common, simple bounds + clip combinations by
-        // converting them to a single RRect draw. It is possible there are other cases
-        // that can be converted.
-        if (crop.contains(bounds)) {
-            const auto insetCrop = crop.makeInset(cornerRadius.x, cornerRadius.y);
-            if (insetCrop.contains(bounds)) {
-                return {SkRRect::MakeRect(bounds), clip}; // clip is empty - no rounding required
-            }
-
-            SkVector radii[4];
-            if (intersectionIsRoundRect(bounds, crop, insetCrop, cornerRadius, radii)) {
-                SkRRect intersectionBounds;
-                intersectionBounds.setRectRadii(bounds, radii);
-                return {intersectionBounds, clip};
-            }
-        }
-
-        // we didn't hit any of our fast paths so set the clip to the cropRect
-        clip.setRectXY(crop, cornerRadius.x, cornerRadius.y);
-    }
-
-    // if we hit this point then we either don't have rounded corners or we are going to rely
-    // on the clip to round the corners for us
-    return {SkRRect::MakeRect(bounds), clip};
-}
-
-inline bool SkiaGLRenderEngine::layerHasBlur(const LayerSettings& layer,
-                                             bool colorTransformModifiesAlpha) {
-    if (layer.backgroundBlurRadius > 0 || layer.blurRegions.size()) {
-        // return false if the content is opaque and would therefore occlude the blur
-        const bool opaqueContent = !layer.source.buffer.buffer || layer.source.buffer.isOpaque;
-        const bool opaqueAlpha = layer.alpha == 1.0f && !colorTransformModifiesAlpha;
-        return layer.skipContentDraw || !(opaqueContent && opaqueAlpha);
-    }
-    return false;
-}
-
-inline SkColor SkiaGLRenderEngine::getSkColor(const vec4& color) {
-    return SkColorSetARGB(color.a * 255, color.r * 255, color.g * 255, color.b * 255);
-}
-
-inline SkM44 SkiaGLRenderEngine::getSkM44(const mat4& matrix) {
-    return SkM44(matrix[0][0], matrix[1][0], matrix[2][0], matrix[3][0],
-                 matrix[0][1], matrix[1][1], matrix[2][1], matrix[3][1],
-                 matrix[0][2], matrix[1][2], matrix[2][2], matrix[3][2],
-                 matrix[0][3], matrix[1][3], matrix[2][3], matrix[3][3]);
-}
-
-inline SkPoint3 SkiaGLRenderEngine::getSkPoint3(const vec3& vector) {
-    return SkPoint3::Make(vector.x, vector.y, vector.z);
-}
-
-size_t SkiaGLRenderEngine::getMaxTextureSize() const {
-    return mGrContext->maxTextureSize();
-}
-
-size_t SkiaGLRenderEngine::getMaxViewportDims() const {
-    return mGrContext->maxRenderTargetSize();
-}
-
-void SkiaGLRenderEngine::drawShadow(SkCanvas* canvas, const SkRRect& casterRRect,
-                                    const ShadowSettings& settings) {
-    ATRACE_CALL();
-    const float casterZ = settings.length / 2.0f;
-    const auto flags =
-            settings.casterIsTranslucent ? kTransparentOccluder_ShadowFlag : kNone_ShadowFlag;
-
-    SkShadowUtils::DrawShadow(canvas, SkPath::RRect(casterRRect), SkPoint3::Make(0, 0, casterZ),
-                              getSkPoint3(settings.lightPos), settings.lightRadius,
-                              getSkColor(settings.ambientColor), getSkColor(settings.spotColor),
-                              flags);
+    return fenceFd;
 }
 
 EGLContext SkiaGLRenderEngine::createEglContext(EGLDisplay display, EGLConfig config,
@@ -1570,114 +519,14 @@
     return value;
 }
 
-void SkiaGLRenderEngine::onActiveDisplaySizeChanged(ui::Size size) {
-    // This cache multiplier was selected based on review of cache sizes relative
-    // to the screen resolution. Looking at the worst case memory needed by blur (~1.5x),
-    // shadows (~1x), and general data structures (e.g. vertex buffers) we selected this as a
-    // conservative default based on that analysis.
-    const float SURFACE_SIZE_MULTIPLIER = 3.5f * bytesPerPixel(mDefaultPixelFormat);
-    const int maxResourceBytes = size.width * size.height * SURFACE_SIZE_MULTIPLIER;
-
-    // start by resizing the current context
-    getActiveGrContext()->setResourceCacheLimit(maxResourceBytes);
-
-    // if it is possible to switch contexts then we will resize the other context
-    const bool originalProtectedState = mInProtectedContext;
-    useProtectedContext(!mInProtectedContext);
-    if (mInProtectedContext != originalProtectedState) {
-        getActiveGrContext()->setResourceCacheLimit(maxResourceBytes);
-        // reset back to the initial context that was active when this method was called
-        useProtectedContext(originalProtectedState);
-    }
-}
-
-void SkiaGLRenderEngine::dump(std::string& result) {
+void SkiaGLRenderEngine::appendBackendSpecificInfoToDump(std::string& result) {
     const gl::GLExtensions& extensions = gl::GLExtensions::getInstance();
-
-    StringAppendF(&result, "\n ------------RE-----------------\n");
+    StringAppendF(&result, "\n ------------RE GLES------------\n");
     StringAppendF(&result, "EGL implementation : %s\n", extensions.getEGLVersion());
     StringAppendF(&result, "%s\n", extensions.getEGLExtensions());
     StringAppendF(&result, "GLES: %s, %s, %s\n", extensions.getVendor(), extensions.getRenderer(),
                   extensions.getVersion());
     StringAppendF(&result, "%s\n", extensions.getExtensions());
-    StringAppendF(&result, "RenderEngine supports protected context: %d\n",
-                  supportsProtectedContent());
-    StringAppendF(&result, "RenderEngine is in protected context: %d\n", mInProtectedContext);
-    StringAppendF(&result, "RenderEngine shaders cached since last dump/primeCache: %d\n",
-                  mSkSLCacheMonitor.shadersCachedSinceLastCall());
-
-    std::vector<ResourcePair> cpuResourceMap = {
-            {"skia/sk_resource_cache/bitmap_", "Bitmaps"},
-            {"skia/sk_resource_cache/rrect-blur_", "Masks"},
-            {"skia/sk_resource_cache/rects-blur_", "Masks"},
-            {"skia/sk_resource_cache/tessellated", "Shadows"},
-            {"skia", "Other"},
-    };
-    SkiaMemoryReporter cpuReporter(cpuResourceMap, false);
-    SkGraphics::DumpMemoryStatistics(&cpuReporter);
-    StringAppendF(&result, "Skia CPU Caches: ");
-    cpuReporter.logTotals(result);
-    cpuReporter.logOutput(result);
-
-    {
-        std::lock_guard<std::mutex> lock(mRenderingMutex);
-
-        std::vector<ResourcePair> gpuResourceMap = {
-                {"texture_renderbuffer", "Texture/RenderBuffer"},
-                {"texture", "Texture"},
-                {"gr_text_blob_cache", "Text"},
-                {"skia", "Other"},
-        };
-        SkiaMemoryReporter gpuReporter(gpuResourceMap, true);
-        mGrContext->dumpMemoryStatistics(&gpuReporter);
-        StringAppendF(&result, "Skia's GPU Caches: ");
-        gpuReporter.logTotals(result);
-        gpuReporter.logOutput(result);
-        StringAppendF(&result, "Skia's Wrapped Objects:\n");
-        gpuReporter.logOutput(result, true);
-
-        StringAppendF(&result, "RenderEngine tracked buffers: %zu\n",
-                      mGraphicBufferExternalRefs.size());
-        StringAppendF(&result, "Dumping buffer ids...\n");
-        for (const auto& [id, refCounts] : mGraphicBufferExternalRefs) {
-            StringAppendF(&result, "- 0x%" PRIx64 " - %d refs \n", id, refCounts);
-        }
-        StringAppendF(&result, "RenderEngine AHB/BackendTexture cache size: %zu\n",
-                      mTextureCache.size());
-        StringAppendF(&result, "Dumping buffer ids...\n");
-        // TODO(178539829): It would be nice to know which layer these are coming from and what
-        // the texture sizes are.
-        for (const auto& [id, unused] : mTextureCache) {
-            StringAppendF(&result, "- 0x%" PRIx64 "\n", id);
-        }
-        StringAppendF(&result, "\n");
-
-        SkiaMemoryReporter gpuProtectedReporter(gpuResourceMap, true);
-        if (mProtectedGrContext) {
-            mProtectedGrContext->dumpMemoryStatistics(&gpuProtectedReporter);
-        }
-        StringAppendF(&result, "Skia's GPU Protected Caches: ");
-        gpuProtectedReporter.logTotals(result);
-        gpuProtectedReporter.logOutput(result);
-        StringAppendF(&result, "Skia's Protected Wrapped Objects:\n");
-        gpuProtectedReporter.logOutput(result, true);
-
-        StringAppendF(&result, "\n");
-        StringAppendF(&result, "RenderEngine runtime effects: %zu\n", mRuntimeEffects.size());
-        for (const auto& [linearEffect, unused] : mRuntimeEffects) {
-            StringAppendF(&result, "- inputDataspace: %s\n",
-                          dataspaceDetails(
-                                  static_cast<android_dataspace>(linearEffect.inputDataspace))
-                                  .c_str());
-            StringAppendF(&result, "- outputDataspace: %s\n",
-                          dataspaceDetails(
-                                  static_cast<android_dataspace>(linearEffect.outputDataspace))
-                                  .c_str());
-            StringAppendF(&result, "undoPremultipliedAlpha: %s\n",
-                          linearEffect.undoPremultipliedAlpha ? "true" : "false");
-        }
-    }
-    StringAppendF(&result, "\n");
 }
 
 } // namespace skia
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.h b/libs/renderengine/skia/SkiaGLRenderEngine.h
index 34f18c1..4a37ffe 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.h
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.h
@@ -55,33 +55,23 @@
     SkiaGLRenderEngine(const RenderEngineCreationArgs& args, EGLDisplay display, EGLContext ctxt,
                        EGLSurface placeholder, EGLContext protectedContext,
                        EGLSurface protectedPlaceholder);
-    ~SkiaGLRenderEngine() override EXCLUDES(mRenderingMutex);
+    ~SkiaGLRenderEngine() override;
 
-    std::future<void> primeCache() override;
-    void cleanupPostRender() override;
-    void cleanFramebufferCache() override{};
     int getContextPriority() override;
-    bool isProtected() const override { return mInProtectedContext; }
-    bool supportsProtectedContent() const override;
-    void useProtectedContext(bool useProtectedContext) override;
-    bool supportsBackgroundBlur() override { return mBlurFilter != nullptr; }
-    void onActiveDisplaySizeChanged(ui::Size size) override;
-    int reportShadersCompiled() override;
 
 protected:
-    void dump(std::string& result) override;
-    size_t getMaxTextureSize() const override;
-    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;
-    void drawLayersInternal(const std::shared_ptr<std::promise<RenderEngineResult>>&& resultPromise,
-                            const DisplaySettings& display,
-                            const std::vector<LayerSettings>& layers,
-                            const std::shared_ptr<ExternalTexture>& buffer,
-                            const bool useFramebufferCache, base::unique_fd&& bufferFence) override;
+    // Implementations of abstract SkiaRenderEngine functions specific to
+    // rendering backend
+    virtual SkiaRenderEngine::Contexts createDirectContexts(const GrContextOptions& options);
+    bool supportsProtectedContentImpl() const override;
+    bool useProtectedContextImpl(GrProtected isProtected) override;
+    void waitFence(GrDirectContext* grContext, base::borrowed_fd fenceFd) override;
+    base::unique_fd flushAndSubmit(GrDirectContext* context) override;
+    void appendBackendSpecificInfoToDump(std::string& result) override;
 
 private:
+    bool waitGpuFence(base::borrowed_fd fenceFd);
+    base::unique_fd flush();
     static EGLConfig chooseEglConfig(EGLDisplay display, int format, bool logConfig);
     static EGLContext createEglContext(EGLDisplay display, EGLConfig config,
                                        EGLContext shareContext,
@@ -89,107 +79,14 @@
                                        Protection protection);
     static std::optional<RenderEngine::ContextPriority> createContextPriority(
             const RenderEngineCreationArgs& args);
-    static EGLSurface createPlaceholderEglPbufferSurface(EGLDisplay display, EGLConfig config,
-                                                         int hwcFormat, Protection protection);
-    inline SkRect getSkRect(const FloatRect& layer);
-    inline SkRect getSkRect(const Rect& layer);
-    inline std::pair<SkRRect, SkRRect> getBoundsAndClip(const FloatRect& bounds,
-                                                        const FloatRect& crop,
-                                                        const vec2& cornerRadius);
-    inline bool layerHasBlur(const LayerSettings& layer, bool colorTransformModifiesAlpha);
-    inline SkColor getSkColor(const vec4& color);
-    inline SkM44 getSkM44(const mat4& matrix);
-    inline SkPoint3 getSkPoint3(const vec3& vector);
-    inline GrDirectContext* getActiveGrContext() const;
-
-    base::unique_fd flush();
-    // waitFence attempts to wait in the GPU, and if unable to waits on the CPU instead.
-    void waitFence(base::borrowed_fd fenceFd);
-    bool waitGpuFence(base::borrowed_fd fenceFd);
-
-    void initCanvas(SkCanvas* canvas, const DisplaySettings& display);
-    void drawShadow(SkCanvas* canvas, const SkRRect& casterRRect,
-                    const ShadowSettings& shadowSettings);
-
-    // If requiresLinearEffect is true or the layer has a stretchEffect a new shader is returned.
-    // Otherwise it returns the input shader.
-    struct RuntimeEffectShaderParameters {
-        sk_sp<SkShader> shader;
-        const LayerSettings& layer;
-        const DisplaySettings& display;
-        bool undoPremultipliedAlpha;
-        bool requiresLinearEffect;
-        float layerDimmingRatio;
-    };
-    sk_sp<SkShader> createRuntimeEffectShader(const RuntimeEffectShaderParameters&);
+    static EGLSurface createPlaceholderEglPbufferSurface(
+            EGLDisplay display, EGLConfig config, int hwcFormat, Protection protection);
 
     EGLDisplay mEGLDisplay;
     EGLContext mEGLContext;
     EGLSurface mPlaceholderSurface;
     EGLContext mProtectedEGLContext;
     EGLSurface mProtectedPlaceholderSurface;
-    BlurFilter* mBlurFilter = nullptr;
-
-    const PixelFormat mDefaultPixelFormat;
-    const bool mUseColorManagement;
-
-    // Identifier used or various mappings of layers to various
-    // textures or shaders
-    using GraphicBufferId = uint64_t;
-
-    // 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, shared between GPU contexts.
-    std::unordered_map<GraphicBufferId, std::shared_ptr<AutoBackendTexture::LocalRef>> mTextureCache
-            GUARDED_BY(mRenderingMutex);
-    std::unordered_map<shaders::LinearEffect, sk_sp<SkRuntimeEffect>, shaders::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.
-    mutable std::mutex mRenderingMutex;
-
-    sp<Fence> mLastDrawFence;
-
-    // Graphics context used for creating surfaces and submitting commands
-    sk_sp<GrDirectContext> mGrContext;
-    // Same as above, but for protected content (eg. DRM)
-    sk_sp<GrDirectContext> mProtectedGrContext;
-
-    bool mInProtectedContext = false;
-    // Object to capture commands send to Skia.
-    std::unique_ptr<SkiaCapture> mCapture;
-
-    // Implements PersistentCache as a way to monitor what SkSL shaders Skia has
-    // cached.
-    class SkSLCacheMonitor : public GrContextOptions::PersistentCache {
-    public:
-        SkSLCacheMonitor() = default;
-        ~SkSLCacheMonitor() override = default;
-
-        sk_sp<SkData> load(const SkData& key) override;
-
-        void store(const SkData& key, const SkData& data, const SkString& description) override;
-
-        int shadersCachedSinceLastCall() {
-            const int shadersCachedSinceLastCall = mShadersCachedSinceLastCall;
-            mShadersCachedSinceLastCall = 0;
-            return shadersCachedSinceLastCall;
-        }
-
-        int totalShadersCompiled() const { return mTotalShadersCompiled; }
-
-    private:
-        int mShadersCachedSinceLastCall = 0;
-        int mTotalShadersCompiled = 0;
-    };
-
-    SkSLCacheMonitor mSkSLCacheMonitor;
 };
 
 } // namespace skia
diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp
index 1fb24f5..db983a8 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaRenderEngine.cpp
@@ -20,16 +20,1244 @@
 
 #include "SkiaRenderEngine.h"
 
+#include <GrBackendSemaphore.h>
+#include <GrContextOptions.h>
+#include <SkBlendMode.h>
+#include <SkCanvas.h>
+#include <SkColor.h>
+#include <SkColorFilter.h>
+#include <SkColorMatrix.h>
+#include <SkColorSpace.h>
+#include <SkData.h>
+#include <SkGraphics.h>
+#include <SkImage.h>
+#include <SkImageFilters.h>
+#include <SkImageInfo.h>
+#include <SkM44.h>
+#include <SkMatrix.h>
+#include <SkPaint.h>
+#include <SkPath.h>
+#include <SkPoint.h>
+#include <SkPoint3.h>
+#include <SkRect.h>
+#include <SkRefCnt.h>
+#include <SkRegion.h>
+#include <SkRRect.h>
+#include <SkRuntimeEffect.h>
+#include <SkSamplingOptions.h>
+#include <SkScalar.h>
+#include <SkShader.h>
+#include <SkShadowUtils.h>
+#include <SkString.h>
+#include <SkSurface.h>
+#include <SkTileMode.h>
 #include <src/core/SkTraceEventCommon.h>
+#include <android-base/stringprintf.h>
+#include <gui/TraceUtils.h>
+#include <sync/sync.h>
+#include <ui/BlurRegion.h>
+#include <ui/DataspaceUtils.h>
+#include <ui/DebugUtils.h>
+#include <ui/GraphicBuffer.h>
+#include <utils/Trace.h>
+
+#include <cmath>
+#include <cstdint>
+#include <memory>
+#include <numeric>
+
+#include "Cache.h"
+#include "ColorSpaces.h"
+#include "filters/BlurFilter.h"
+#include "filters/GaussianBlurFilter.h"
+#include "filters/KawaseBlurFilter.h"
+#include "filters/LinearEffect.h"
+#include "log/log_main.h"
+#include "skia/debug/SkiaCapture.h"
+#include "skia/debug/SkiaMemoryReporter.h"
+#include "skia/filters/StretchShaderFactory.h"
+#include "system/graphics-base-v1.0.h"
+
+namespace {
+
+// Debugging settings
+static const bool kPrintLayerSettings = false;
+static const bool kFlushAfterEveryLayer = kPrintLayerSettings;
+
+} // namespace
+
+// Utility functions related to SkRect
+
+namespace {
+
+static inline SkRect getSkRect(const android::FloatRect& rect) {
+    return SkRect::MakeLTRB(rect.left, rect.top, rect.right, rect.bottom);
+}
+
+static inline SkRect getSkRect(const android::Rect& rect) {
+    return SkRect::MakeLTRB(rect.left, rect.top, rect.right, rect.bottom);
+}
+
+/**
+ *  Verifies that common, simple bounds + clip combinations can be converted into
+ *  a single RRect draw call returning true if possible. If true the radii parameter
+ *  will be filled with the correct radii values that combined with bounds param will
+ *  produce the insected roundRect. If false, the returned state of the radii param is undefined.
+ */
+static bool intersectionIsRoundRect(const SkRect& bounds, const SkRect& crop,
+                                    const SkRect& insetCrop, const android::vec2& cornerRadius,
+                                    SkVector radii[4]) {
+    const bool leftEqual = bounds.fLeft == crop.fLeft;
+    const bool topEqual = bounds.fTop == crop.fTop;
+    const bool rightEqual = bounds.fRight == crop.fRight;
+    const bool bottomEqual = bounds.fBottom == crop.fBottom;
+
+    // In the event that the corners of the bounds only partially align with the crop we
+    // need to ensure that the resulting shape can still be represented as a round rect.
+    // In particular the round rect implementation will scale the value of all corner radii
+    // if the sum of the radius along any edge is greater than the length of that edge.
+    // See https://www.w3.org/TR/css-backgrounds-3/#corner-overlap
+    const bool requiredWidth = bounds.width() > (cornerRadius.x * 2);
+    const bool requiredHeight = bounds.height() > (cornerRadius.y * 2);
+    if (!requiredWidth || !requiredHeight) {
+        return false;
+    }
+
+    // Check each cropped corner to ensure that it exactly matches the crop or its corner is
+    // contained within the cropped shape and does not need rounded.
+    // compute the UpperLeft corner radius
+    if (leftEqual && topEqual) {
+        radii[0].set(cornerRadius.x, cornerRadius.y);
+    } else if ((leftEqual && bounds.fTop >= insetCrop.fTop) ||
+               (topEqual && bounds.fLeft >= insetCrop.fLeft)) {
+        radii[0].set(0, 0);
+    } else {
+        return false;
+    }
+    // compute the UpperRight corner radius
+    if (rightEqual && topEqual) {
+        radii[1].set(cornerRadius.x, cornerRadius.y);
+    } else if ((rightEqual && bounds.fTop >= insetCrop.fTop) ||
+               (topEqual && bounds.fRight <= insetCrop.fRight)) {
+        radii[1].set(0, 0);
+    } else {
+        return false;
+    }
+    // compute the BottomRight corner radius
+    if (rightEqual && bottomEqual) {
+        radii[2].set(cornerRadius.x, cornerRadius.y);
+    } else if ((rightEqual && bounds.fBottom <= insetCrop.fBottom) ||
+               (bottomEqual && bounds.fRight <= insetCrop.fRight)) {
+        radii[2].set(0, 0);
+    } else {
+        return false;
+    }
+    // compute the BottomLeft corner radius
+    if (leftEqual && bottomEqual) {
+        radii[3].set(cornerRadius.x, cornerRadius.y);
+    } else if ((leftEqual && bounds.fBottom <= insetCrop.fBottom) ||
+               (bottomEqual && bounds.fLeft >= insetCrop.fLeft)) {
+        radii[3].set(0, 0);
+    } else {
+        return false;
+    }
+
+    return true;
+}
+
+static inline std::pair<SkRRect, SkRRect> getBoundsAndClip(const android::FloatRect& boundsRect,
+                                                           const android::FloatRect& cropRect,
+                                                           const android::vec2& cornerRadius) {
+    const SkRect bounds = getSkRect(boundsRect);
+    const SkRect crop = getSkRect(cropRect);
+
+    SkRRect clip;
+    if (cornerRadius.x > 0 && cornerRadius.y > 0) {
+        // it the crop and the bounds are equivalent or there is no crop then we don't need a clip
+        if (bounds == crop || crop.isEmpty()) {
+            return {SkRRect::MakeRectXY(bounds, cornerRadius.x, cornerRadius.y), clip};
+        }
+
+        // This makes an effort to speed up common, simple bounds + clip combinations by
+        // converting them to a single RRect draw. It is possible there are other cases
+        // that can be converted.
+        if (crop.contains(bounds)) {
+            const auto insetCrop = crop.makeInset(cornerRadius.x, cornerRadius.y);
+            if (insetCrop.contains(bounds)) {
+                return {SkRRect::MakeRect(bounds), clip}; // clip is empty - no rounding required
+            }
+
+            SkVector radii[4];
+            if (intersectionIsRoundRect(bounds, crop, insetCrop, cornerRadius, radii)) {
+                SkRRect intersectionBounds;
+                intersectionBounds.setRectRadii(bounds, radii);
+                return {intersectionBounds, clip};
+            }
+        }
+
+        // we didn't hit any of our fast paths so set the clip to the cropRect
+        clip.setRectXY(crop, cornerRadius.x, cornerRadius.y);
+    }
+
+    // if we hit this point then we either don't have rounded corners or we are going to rely
+    // on the clip to round the corners for us
+    return {SkRRect::MakeRect(bounds), clip};
+}
+
+static inline bool layerHasBlur(const android::renderengine::LayerSettings& layer,
+                                bool colorTransformModifiesAlpha) {
+    if (layer.backgroundBlurRadius > 0 || layer.blurRegions.size()) {
+        // return false if the content is opaque and would therefore occlude the blur
+        const bool opaqueContent = !layer.source.buffer.buffer || layer.source.buffer.isOpaque;
+        const bool opaqueAlpha = layer.alpha == 1.0f && !colorTransformModifiesAlpha;
+        return layer.skipContentDraw || !(opaqueContent && opaqueAlpha);
+    }
+    return false;
+}
+
+static inline SkColor getSkColor(const android::vec4& color) {
+    return SkColorSetARGB(color.a * 255, color.r * 255, color.g * 255, color.b * 255);
+}
+
+static inline SkM44 getSkM44(const android::mat4& matrix) {
+    return SkM44(matrix[0][0], matrix[1][0], matrix[2][0], matrix[3][0],
+                 matrix[0][1], matrix[1][1], matrix[2][1], matrix[3][1],
+                 matrix[0][2], matrix[1][2], matrix[2][2], matrix[3][2],
+                 matrix[0][3], matrix[1][3], matrix[2][3], matrix[3][3]);
+}
+
+static inline SkPoint3 getSkPoint3(const android::vec3& vector) {
+    return SkPoint3::Make(vector.x, vector.y, vector.z);
+}
+
+} // namespace
 
 namespace android {
 namespace renderengine {
 namespace skia {
-SkiaRenderEngine::SkiaRenderEngine(RenderEngineType type) : RenderEngine(type) {}
+
+using base::StringAppendF;
+
+std::future<void> SkiaRenderEngine::primeCache() {
+    Cache::primeShaderCache(this);
+    return {};
+}
+
+sk_sp<SkData> SkiaRenderEngine::SkSLCacheMonitor::load(const SkData& key) {
+    // This "cache" does not actually cache anything. It just allows us to
+    // monitor Skia's internal cache. So this method always returns null.
+    return nullptr;
+}
+
+void SkiaRenderEngine::SkSLCacheMonitor::store(const SkData& key, const SkData& data,
+                                               const SkString& description) {
+    mShadersCachedSinceLastCall++;
+    mTotalShadersCompiled++;
+    ATRACE_FORMAT("SF cache: %i shaders", mTotalShadersCompiled);
+}
+
+int SkiaRenderEngine::reportShadersCompiled() {
+    return mSkSLCacheMonitor.totalShadersCompiled();
+}
 
 void SkiaRenderEngine::setEnableTracing(bool tracingEnabled) {
     SkAndroidFrameworkTraceUtil::setEnableTracing(tracingEnabled);
 }
+
+SkiaRenderEngine::SkiaRenderEngine(
+    RenderEngineType type,
+    PixelFormat pixelFormat,
+    bool useColorManagement,
+    bool supportsBackgroundBlur) :
+    RenderEngine(type),
+    mDefaultPixelFormat(pixelFormat),
+    mUseColorManagement(useColorManagement) {
+    if (supportsBackgroundBlur) {
+        ALOGD("Background Blurs Enabled");
+        mBlurFilter = new KawaseBlurFilter();
+    }
+    mCapture = std::make_unique<SkiaCapture>();
+}
+
+SkiaRenderEngine::~SkiaRenderEngine() { }
+
+// To be called from backend dtors.
+void SkiaRenderEngine::finishRenderingAndAbandonContext() {
+    std::lock_guard<std::mutex> lock(mRenderingMutex);
+
+    if (mBlurFilter) {
+        delete mBlurFilter;
+    }
+
+    if (mGrContext) {
+        mGrContext->flushAndSubmit(true);
+        mGrContext->abandonContext();
+    }
+
+    if (mProtectedGrContext) {
+        mProtectedGrContext->flushAndSubmit(true);
+        mProtectedGrContext->abandonContext();
+    }
+}
+
+void SkiaRenderEngine::useProtectedContext(bool useProtectedContext) {
+    if (useProtectedContext == mInProtectedContext ||
+        (useProtectedContext && !supportsProtectedContent())) {
+        return;
+    }
+
+    // release any scratch resources before switching into a new mode
+    if (getActiveGrContext()) {
+        getActiveGrContext()->purgeUnlockedResources(true);
+    }
+
+    // Backend-specific way to switch to protected context
+    if (useProtectedContextImpl(
+            useProtectedContext ? GrProtected::kYes : GrProtected::kNo)) {
+        mInProtectedContext = useProtectedContext;
+        // given that we are sharing the same thread between two GrContexts we need to
+        // make sure that the thread state is reset when switching between the two.
+        if (getActiveGrContext()) {
+            getActiveGrContext()->resetContext();
+        }
+    }
+}
+
+GrDirectContext* SkiaRenderEngine::getActiveGrContext() {
+    return mInProtectedContext ? mProtectedGrContext.get() : mGrContext.get();
+}
+
+static float toDegrees(uint32_t transform) {
+    switch (transform) {
+        case ui::Transform::ROT_90:
+            return 90.0;
+        case ui::Transform::ROT_180:
+            return 180.0;
+        case ui::Transform::ROT_270:
+            return 270.0;
+        default:
+            return 0.0;
+    }
+}
+
+static SkColorMatrix toSkColorMatrix(const android::mat4& matrix) {
+    return SkColorMatrix(matrix[0][0], matrix[1][0], matrix[2][0], matrix[3][0], 0, matrix[0][1],
+                         matrix[1][1], matrix[2][1], matrix[3][1], 0, matrix[0][2], matrix[1][2],
+                         matrix[2][2], matrix[3][2], 0, matrix[0][3], matrix[1][3], matrix[2][3],
+                         matrix[3][3], 0);
+}
+
+static bool needsToneMapping(ui::Dataspace sourceDataspace, ui::Dataspace destinationDataspace) {
+    int64_t sourceTransfer = sourceDataspace & HAL_DATASPACE_TRANSFER_MASK;
+    int64_t destTransfer = destinationDataspace & HAL_DATASPACE_TRANSFER_MASK;
+
+    // Treat unsupported dataspaces as srgb
+    if (destTransfer != HAL_DATASPACE_TRANSFER_LINEAR &&
+        destTransfer != HAL_DATASPACE_TRANSFER_HLG &&
+        destTransfer != HAL_DATASPACE_TRANSFER_ST2084) {
+        destTransfer = HAL_DATASPACE_TRANSFER_SRGB;
+    }
+
+    if (sourceTransfer != HAL_DATASPACE_TRANSFER_LINEAR &&
+        sourceTransfer != HAL_DATASPACE_TRANSFER_HLG &&
+        sourceTransfer != HAL_DATASPACE_TRANSFER_ST2084) {
+        sourceTransfer = HAL_DATASPACE_TRANSFER_SRGB;
+    }
+
+    const bool isSourceLinear = sourceTransfer == HAL_DATASPACE_TRANSFER_LINEAR;
+    const bool isSourceSRGB = sourceTransfer == HAL_DATASPACE_TRANSFER_SRGB;
+    const bool isDestLinear = destTransfer == HAL_DATASPACE_TRANSFER_LINEAR;
+    const bool isDestSRGB = destTransfer == HAL_DATASPACE_TRANSFER_SRGB;
+
+    return !(isSourceLinear && isDestSRGB) && !(isSourceSRGB && isDestLinear) &&
+            sourceTransfer != destTransfer;
+}
+
+void SkiaRenderEngine::ensureGrContextsCreated() {
+    if (mGrContext) {
+        return;
+    }
+
+    GrContextOptions options;
+    options.fDisableDriverCorrectnessWorkarounds = true;
+    options.fDisableDistanceFieldPaths = true;
+    options.fReducedShaderVariations = true;
+    options.fPersistentCache = &mSkSLCacheMonitor;
+    std::tie(mGrContext, mProtectedGrContext) = createDirectContexts(options);
+}
+
+void SkiaRenderEngine::mapExternalTextureBuffer(const sp<GraphicBuffer>& buffer,
+                                                  bool isRenderable) {
+    // Only run this if RE is running on its own thread. This way the access to GL
+    // operations is guaranteed to be happening on the same thread.
+    if (mRenderEngineType != RenderEngineType::SKIA_GL_THREADED) {
+        return;
+    }
+    // We currently don't attempt to map a buffer if the buffer contains protected content
+    // because GPU resources for protected buffers is much more limited.
+    const bool isProtectedBuffer = buffer->getUsage() & GRALLOC_USAGE_PROTECTED;
+    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).  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;
+
+    std::lock_guard<std::mutex> lock(mRenderingMutex);
+    mGraphicBufferExternalRefs[buffer->getId()]++;
+
+    if (const auto& iter = cache.find(buffer->getId()); iter == cache.end()) {
+        std::shared_ptr<AutoBackendTexture::LocalRef> imageTextureRef =
+                std::make_shared<AutoBackendTexture::LocalRef>(grContext,
+                                                               buffer->toAHardwareBuffer(),
+                                                               isRenderable, mTextureCleanupMgr);
+        cache.insert({buffer->getId(), imageTextureRef});
+    }
+}
+
+void SkiaRenderEngine::unmapExternalTextureBuffer(const sp<GraphicBuffer>& buffer) {
+    ATRACE_CALL();
+    std::lock_guard<std::mutex> lock(mRenderingMutex);
+    if (const auto& iter = mGraphicBufferExternalRefs.find(buffer->getId());
+        iter != mGraphicBufferExternalRefs.end()) {
+        if (iter->second == 0) {
+            ALOGW("Attempted to unmap GraphicBuffer <id: %" PRId64
+                  "> from RenderEngine texture, but the "
+                  "ref count was already zero!",
+                  buffer->getId());
+            mGraphicBufferExternalRefs.erase(buffer->getId());
+            return;
+        }
+
+        iter->second--;
+
+        // Swap contexts if needed prior to deleting this buffer
+        // See Issue 1 of
+        // https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_protected_content.txt: even
+        // when a protected context and an unprotected context are part of the same share group,
+        // protected surfaces may not be accessed by an unprotected context, implying that protected
+        // surfaces may only be freed when a protected context is active.
+        const bool inProtected = mInProtectedContext;
+        useProtectedContext(buffer->getUsage() & GRALLOC_USAGE_PROTECTED);
+
+        if (iter->second == 0) {
+            mTextureCache.erase(buffer->getId());
+            mGraphicBufferExternalRefs.erase(buffer->getId());
+        }
+
+        // Swap back to the previous context so that cached values of isProtected in SurfaceFlinger
+        // are up-to-date.
+        if (inProtected != mInProtectedContext) {
+            useProtectedContext(inProtected);
+        }
+    }
+}
+
+bool SkiaRenderEngine::canSkipPostRenderCleanup() const {
+    std::lock_guard<std::mutex> lock(mRenderingMutex);
+    return mTextureCleanupMgr.isEmpty();
+}
+
+void SkiaRenderEngine::cleanupPostRender() {
+    ATRACE_CALL();
+    std::lock_guard<std::mutex> lock(mRenderingMutex);
+    mTextureCleanupMgr.cleanup();
+}
+
+sk_sp<SkShader> SkiaRenderEngine::createRuntimeEffectShader(
+        const RuntimeEffectShaderParameters& parameters) {
+    // The given surface will be stretched by HWUI via matrix transformation
+    // which gets similar results for most surfaces
+    // Determine later on if we need to leverage the stertch shader within
+    // surface flinger
+    const auto& stretchEffect = parameters.layer.stretchEffect;
+    auto shader = parameters.shader;
+    if (stretchEffect.hasEffect()) {
+        const auto targetBuffer = parameters.layer.source.buffer.buffer;
+        const auto graphicBuffer = targetBuffer ? targetBuffer->getBuffer() : nullptr;
+        if (graphicBuffer && parameters.shader) {
+            shader = mStretchShaderFactory.createSkShader(shader, stretchEffect);
+        }
+    }
+
+    if (parameters.requiresLinearEffect) {
+        const ui::Dataspace inputDataspace = mUseColorManagement ? parameters.layer.sourceDataspace
+                                                                 : ui::Dataspace::V0_SRGB_LINEAR;
+        const ui::Dataspace outputDataspace = mUseColorManagement
+                ? parameters.display.outputDataspace
+                : ui::Dataspace::V0_SRGB_LINEAR;
+
+        auto effect =
+                shaders::LinearEffect{.inputDataspace = inputDataspace,
+                                      .outputDataspace = outputDataspace,
+                                      .undoPremultipliedAlpha = parameters.undoPremultipliedAlpha};
+
+        auto effectIter = mRuntimeEffects.find(effect);
+        sk_sp<SkRuntimeEffect> runtimeEffect = nullptr;
+        if (effectIter == mRuntimeEffects.end()) {
+            runtimeEffect = buildRuntimeEffect(effect);
+            mRuntimeEffects.insert({effect, runtimeEffect});
+        } else {
+            runtimeEffect = effectIter->second;
+        }
+        mat4 colorTransform = parameters.layer.colorTransform;
+
+        colorTransform *=
+                mat4::scale(vec4(parameters.layerDimmingRatio, parameters.layerDimmingRatio,
+                                 parameters.layerDimmingRatio, 1.f));
+        const auto targetBuffer = parameters.layer.source.buffer.buffer;
+        const auto graphicBuffer = targetBuffer ? targetBuffer->getBuffer() : nullptr;
+        const auto hardwareBuffer = graphicBuffer ? graphicBuffer->toAHardwareBuffer() : nullptr;
+        return createLinearEffectShader(parameters.shader, effect, runtimeEffect, colorTransform,
+                                        parameters.display.maxLuminance,
+                                        parameters.display.currentLuminanceNits,
+                                        parameters.layer.source.buffer.maxLuminanceNits,
+                                        hardwareBuffer, parameters.display.renderIntent);
+    }
+    return parameters.shader;
+}
+
+void SkiaRenderEngine::initCanvas(SkCanvas* canvas, const DisplaySettings& display) {
+    if (CC_UNLIKELY(mCapture->isCaptureRunning())) {
+        // Record display settings when capture is running.
+        std::stringstream displaySettings;
+        PrintTo(display, &displaySettings);
+        // Store the DisplaySettings in additional information.
+        canvas->drawAnnotation(SkRect::MakeEmpty(), "DisplaySettings",
+                               SkData::MakeWithCString(displaySettings.str().c_str()));
+    }
+
+    // Before doing any drawing, let's make sure that we'll start at the origin of the display.
+    // Some displays don't start at 0,0 for example when we're mirroring the screen. Also, virtual
+    // displays might have different scaling when compared to the physical screen.
+
+    canvas->clipRect(getSkRect(display.physicalDisplay));
+    canvas->translate(display.physicalDisplay.left, display.physicalDisplay.top);
+
+    const auto clipWidth = display.clip.width();
+    const auto clipHeight = display.clip.height();
+    auto rotatedClipWidth = clipWidth;
+    auto rotatedClipHeight = clipHeight;
+    // Scale is contingent on the rotation result.
+    if (display.orientation & ui::Transform::ROT_90) {
+        std::swap(rotatedClipWidth, rotatedClipHeight);
+    }
+    const auto scaleX = static_cast<SkScalar>(display.physicalDisplay.width()) /
+            static_cast<SkScalar>(rotatedClipWidth);
+    const auto scaleY = static_cast<SkScalar>(display.physicalDisplay.height()) /
+            static_cast<SkScalar>(rotatedClipHeight);
+    canvas->scale(scaleX, scaleY);
+
+    // Canvas rotation is done by centering the clip window at the origin, rotating, translating
+    // back so that the top left corner of the clip is at (0, 0).
+    canvas->translate(rotatedClipWidth / 2, rotatedClipHeight / 2);
+    canvas->rotate(toDegrees(display.orientation));
+    canvas->translate(-clipWidth / 2, -clipHeight / 2);
+    canvas->translate(-display.clip.left, -display.clip.top);
+}
+
+class AutoSaveRestore {
+public:
+    AutoSaveRestore(SkCanvas* canvas) : mCanvas(canvas) { mSaveCount = canvas->save(); }
+    ~AutoSaveRestore() { restore(); }
+    void replace(SkCanvas* canvas) {
+        mCanvas = canvas;
+        mSaveCount = canvas->save();
+    }
+    void restore() {
+        if (mCanvas) {
+            mCanvas->restoreToCount(mSaveCount);
+            mCanvas = nullptr;
+        }
+    }
+
+private:
+    SkCanvas* mCanvas;
+    int mSaveCount;
+};
+
+static SkRRect getBlurRRect(const BlurRegion& region) {
+    const auto rect = SkRect::MakeLTRB(region.left, region.top, region.right, region.bottom);
+    const SkVector radii[4] = {SkVector::Make(region.cornerRadiusTL, region.cornerRadiusTL),
+                               SkVector::Make(region.cornerRadiusTR, region.cornerRadiusTR),
+                               SkVector::Make(region.cornerRadiusBR, region.cornerRadiusBR),
+                               SkVector::Make(region.cornerRadiusBL, region.cornerRadiusBL)};
+    SkRRect roundedRect;
+    roundedRect.setRectRadii(rect, radii);
+    return roundedRect;
+}
+
+// Arbitrary default margin which should be close enough to zero.
+constexpr float kDefaultMargin = 0.0001f;
+static bool equalsWithinMargin(float expected, float value, float margin = kDefaultMargin) {
+    LOG_ALWAYS_FATAL_IF(margin < 0.f, "Margin is negative!");
+    return std::abs(expected - value) < margin;
+}
+
+namespace {
+template <typename T>
+void logSettings(const T& t) {
+    std::stringstream stream;
+    PrintTo(t, &stream);
+    auto string = stream.str();
+    size_t pos = 0;
+    // Perfetto ignores \n, so split up manually into separate ALOGD statements.
+    const size_t size = string.size();
+    while (pos < size) {
+        const size_t end = std::min(string.find("\n", pos), size);
+        ALOGD("%s", string.substr(pos, end - pos).c_str());
+        pos = end + 1;
+    }
+}
+} // namespace
+
+// 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;
+};
+
+void SkiaRenderEngine::drawLayersInternal(
+        const std::shared_ptr<std::promise<RenderEngineResult>>&& resultPromise,
+        const DisplaySettings& display, const std::vector<LayerSettings>& layers,
+        const std::shared_ptr<ExternalTexture>& buffer, const bool /*useFramebufferCache*/,
+        base::unique_fd&& bufferFence) {
+    ATRACE_NAME("SkiaGL::drawLayersInternal");
+
+    std::lock_guard<std::mutex> lock(mRenderingMutex);
+
+    if (buffer == nullptr) {
+        ALOGE("No output buffer provided. Aborting GPU composition.");
+        resultPromise->set_value({BAD_VALUE, base::unique_fd()});
+        return;
+    }
+
+    validateOutputBufferUsage(buffer->getBuffer());
+
+    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;
+    } else {
+        surfaceTextureRef =
+                std::make_shared<AutoBackendTexture::LocalRef>(grContext,
+                                                               buffer->getBuffer()
+                                                                       ->toAHardwareBuffer(),
+                                                               true, mTextureCleanupMgr);
+    }
+
+    // wait on the buffer to be ready to use prior to using it
+    waitFence(grContext, bufferFence);
+
+    const ui::Dataspace dstDataspace =
+            mUseColorManagement ? display.outputDataspace : ui::Dataspace::V0_SRGB_LINEAR;
+    sk_sp<SkSurface> dstSurface = surfaceTextureRef->getOrCreateSurface(dstDataspace, grContext);
+
+    SkCanvas* dstCanvas = mCapture->tryCapture(dstSurface.get());
+    if (dstCanvas == nullptr) {
+        ALOGE("Cannot acquire canvas from Skia.");
+        resultPromise->set_value({BAD_VALUE, base::unique_fd()});
+        return;
+    }
+
+    // setup color filter if necessary
+    sk_sp<SkColorFilter> displayColorTransform;
+    if (display.colorTransform != mat4() && !display.deviceHandlesColorTransform) {
+        displayColorTransform = SkColorFilters::Matrix(toSkColorMatrix(display.colorTransform));
+    }
+    const bool ctModifiesAlpha =
+            displayColorTransform && !displayColorTransform->isAlphaUnchanged();
+
+    // Find the max layer white point to determine the max luminance of the scene...
+    const float maxLayerWhitePoint = std::transform_reduce(
+            layers.cbegin(), layers.cend(), 0.f,
+            [](float left, float right) { return std::max(left, right); },
+            [&](const auto& l) { return l.whitePointNits; });
+
+    // ...and compute the dimming ratio if dimming is requested
+    const float displayDimmingRatio = display.targetLuminanceNits > 0.f &&
+                    maxLayerWhitePoint > 0.f && display.targetLuminanceNits > maxLayerWhitePoint
+            ? maxLayerWhitePoint / display.targetLuminanceNits
+            : 1.f;
+
+    // Find if any layers have requested blur, we'll use that info to decide when to render to an
+    // offscreen buffer and when to render to the native buffer.
+    sk_sp<SkSurface> activeSurface(dstSurface);
+    SkCanvas* canvas = dstCanvas;
+    SkiaCapture::OffscreenState offscreenCaptureState;
+    const LayerSettings* blurCompositionLayer = nullptr;
+    if (mBlurFilter) {
+        bool requiresCompositionLayer = false;
+        for (const auto& layer : layers) {
+            // if the layer doesn't have blur or it is not visible then continue
+            if (!layerHasBlur(layer, ctModifiesAlpha)) {
+                continue;
+            }
+            if (layer.backgroundBlurRadius > 0 &&
+                layer.backgroundBlurRadius < mBlurFilter->getMaxCrossFadeRadius()) {
+                requiresCompositionLayer = true;
+            }
+            for (auto region : layer.blurRegions) {
+                if (region.blurRadius < mBlurFilter->getMaxCrossFadeRadius()) {
+                    requiresCompositionLayer = true;
+                }
+            }
+            if (requiresCompositionLayer) {
+                activeSurface = dstSurface->makeSurface(dstSurface->imageInfo());
+                canvas = mCapture->tryOffscreenCapture(activeSurface.get(), &offscreenCaptureState);
+                blurCompositionLayer = &layer;
+                break;
+            }
+        }
+    }
+
+    AutoSaveRestore surfaceAutoSaveRestore(canvas);
+    // Clear the entire canvas with a transparent black to prevent ghost images.
+    canvas->clear(SK_ColorTRANSPARENT);
+    initCanvas(canvas, display);
+
+    if (kPrintLayerSettings) {
+        logSettings(display);
+    }
+    for (const auto& layer : layers) {
+        ATRACE_FORMAT("DrawLayer: %s", layer.name.c_str());
+
+        if (kPrintLayerSettings) {
+            logSettings(layer);
+        }
+
+        sk_sp<SkImage> blurInput;
+        if (blurCompositionLayer == &layer) {
+            LOG_ALWAYS_FATAL_IF(activeSurface == dstSurface);
+            LOG_ALWAYS_FATAL_IF(canvas == dstCanvas);
+
+            // save a snapshot of the activeSurface to use as input to the blur shaders
+            blurInput = activeSurface->makeImageSnapshot();
+
+            // blit the offscreen framebuffer into the destination AHB, but only
+            // if there are blur regions. backgroundBlurRadius blurs the entire
+            // image below, so it can skip this step.
+            if (layer.blurRegions.size()) {
+                SkPaint paint;
+                paint.setBlendMode(SkBlendMode::kSrc);
+                if (CC_UNLIKELY(mCapture->isCaptureRunning())) {
+                    uint64_t id = mCapture->endOffscreenCapture(&offscreenCaptureState);
+                    dstCanvas->drawAnnotation(SkRect::Make(dstCanvas->imageInfo().dimensions()),
+                                              String8::format("SurfaceID|%" PRId64, id).c_str(),
+                                              nullptr);
+                    dstCanvas->drawImage(blurInput, 0, 0, SkSamplingOptions(), &paint);
+                } else {
+                    activeSurface->draw(dstCanvas, 0, 0, SkSamplingOptions(), &paint);
+                }
+            }
+
+            // assign dstCanvas to canvas and ensure that the canvas state is up to date
+            canvas = dstCanvas;
+            surfaceAutoSaveRestore.replace(canvas);
+            initCanvas(canvas, display);
+
+            LOG_ALWAYS_FATAL_IF(activeSurface->getCanvas()->getSaveCount() !=
+                                dstSurface->getCanvas()->getSaveCount());
+            LOG_ALWAYS_FATAL_IF(activeSurface->getCanvas()->getTotalMatrix() !=
+                                dstSurface->getCanvas()->getTotalMatrix());
+
+            // assign dstSurface to activeSurface
+            activeSurface = dstSurface;
+        }
+
+        SkAutoCanvasRestore layerAutoSaveRestore(canvas, true);
+        if (CC_UNLIKELY(mCapture->isCaptureRunning())) {
+            // Record the name of the layer if the capture is running.
+            std::stringstream layerSettings;
+            PrintTo(layer, &layerSettings);
+            // Store the LayerSettings in additional information.
+            canvas->drawAnnotation(SkRect::MakeEmpty(), layer.name.c_str(),
+                                   SkData::MakeWithCString(layerSettings.str().c_str()));
+        }
+        // Layers have a local transform that should be applied to them
+        canvas->concat(getSkM44(layer.geometry.positionTransform).asM33());
+
+        const auto [bounds, roundRectClip] =
+                getBoundsAndClip(layer.geometry.boundaries, layer.geometry.roundedCornersCrop,
+                                 layer.geometry.roundedCornersRadius);
+        if (mBlurFilter && layerHasBlur(layer, ctModifiesAlpha)) {
+            std::unordered_map<uint32_t, sk_sp<SkImage>> cachedBlurs;
+
+            // if multiple layers have blur, then we need to take a snapshot now because
+            // only the lowest layer will have blurImage populated earlier
+            if (!blurInput) {
+                blurInput = activeSurface->makeImageSnapshot();
+            }
+            // rect to be blurred in the coordinate space of blurInput
+            const auto blurRect = canvas->getTotalMatrix().mapRect(bounds.rect());
+
+            // if the clip needs to be applied then apply it now and make sure
+            // it is restored before we attempt to draw any shadows.
+            SkAutoCanvasRestore acr(canvas, true);
+            if (!roundRectClip.isEmpty()) {
+                canvas->clipRRect(roundRectClip, true);
+            }
+
+            // TODO(b/182216890): Filter out empty layers earlier
+            if (blurRect.width() > 0 && blurRect.height() > 0) {
+                if (layer.backgroundBlurRadius > 0) {
+                    ATRACE_NAME("BackgroundBlur");
+                    auto blurredImage = mBlurFilter->generate(grContext, layer.backgroundBlurRadius,
+                                                              blurInput, blurRect);
+
+                    cachedBlurs[layer.backgroundBlurRadius] = blurredImage;
+
+                    mBlurFilter->drawBlurRegion(canvas, bounds, layer.backgroundBlurRadius, 1.0f,
+                                                blurRect, blurredImage, blurInput);
+                }
+
+                canvas->concat(getSkM44(layer.blurRegionTransform).asM33());
+                for (auto region : layer.blurRegions) {
+                    if (cachedBlurs[region.blurRadius] == nullptr) {
+                        ATRACE_NAME("BlurRegion");
+                        cachedBlurs[region.blurRadius] =
+                                mBlurFilter->generate(grContext, region.blurRadius, blurInput,
+                                                      blurRect);
+                    }
+
+                    mBlurFilter->drawBlurRegion(canvas, getBlurRRect(region), region.blurRadius,
+                                                region.alpha, blurRect,
+                                                cachedBlurs[region.blurRadius], blurInput);
+                }
+            }
+        }
+
+        if (layer.shadow.length > 0) {
+            // This would require a new parameter/flag to SkShadowUtils::DrawShadow
+            LOG_ALWAYS_FATAL_IF(layer.disableBlending, "Cannot disableBlending with a shadow");
+
+            SkRRect shadowBounds, shadowClip;
+            if (layer.geometry.boundaries == layer.shadow.boundaries) {
+                shadowBounds = bounds;
+                shadowClip = roundRectClip;
+            } else {
+                std::tie(shadowBounds, shadowClip) =
+                        getBoundsAndClip(layer.shadow.boundaries, layer.geometry.roundedCornersCrop,
+                                         layer.geometry.roundedCornersRadius);
+            }
+
+            // Technically, if bounds is a rect and roundRectClip is not empty,
+            // it means that the bounds and roundedCornersCrop were different
+            // enough that we should intersect them to find the proper shadow.
+            // In practice, this often happens when the two rectangles appear to
+            // not match due to rounding errors. Draw the rounded version, which
+            // looks more like the intent.
+            const auto& rrect =
+                    shadowBounds.isRect() && !shadowClip.isEmpty() ? shadowClip : shadowBounds;
+            drawShadow(canvas, rrect, layer.shadow);
+        }
+
+        const float layerDimmingRatio = layer.whitePointNits <= 0.f
+                ? displayDimmingRatio
+                : (layer.whitePointNits / maxLayerWhitePoint) * displayDimmingRatio;
+
+        const bool dimInLinearSpace = display.dimmingStage !=
+                aidl::android::hardware::graphics::composer3::DimmingStage::GAMMA_OETF;
+
+        const bool requiresLinearEffect = layer.colorTransform != mat4() ||
+                (mUseColorManagement &&
+                 needsToneMapping(layer.sourceDataspace, display.outputDataspace)) ||
+                (dimInLinearSpace && !equalsWithinMargin(1.f, layerDimmingRatio));
+
+        // quick abort from drawing the remaining portion of the layer
+        if (layer.skipContentDraw ||
+            (layer.alpha == 0 && !requiresLinearEffect && !layer.disableBlending &&
+             (!displayColorTransform || displayColorTransform->isAlphaUnchanged()))) {
+            continue;
+        }
+
+        // If we need to map to linear space or color management is disabled, then mark the source
+        // image with the same colorspace as the destination surface so that Skia's color
+        // management is a no-op.
+        const ui::Dataspace layerDataspace = (!mUseColorManagement || requiresLinearEffect)
+                ? dstDataspace
+                : layer.sourceDataspace;
+
+        SkPaint paint;
+        if (layer.source.buffer.buffer) {
+            ATRACE_NAME("DrawImage");
+            validateInputBufferUsage(layer.source.buffer.buffer->getBuffer());
+            const auto& item = layer.source.buffer;
+            std::shared_ptr<AutoBackendTexture::LocalRef> imageTextureRef = nullptr;
+
+            if (const auto& iter = cache.find(item.buffer->getBuffer()->getId());
+                iter != cache.end()) {
+                imageTextureRef = iter->second;
+            } else {
+                // If we didn't find the image in the cache, then create a local ref but don't cache
+                // it. If we're using skia, we're guaranteed to run on a dedicated GPU thread so if
+                // we didn't find anything in the cache then we intentionally did not cache this
+                // buffer's resources.
+                imageTextureRef = std::make_shared<
+                        AutoBackendTexture::LocalRef>(grContext,
+                                                      item.buffer->getBuffer()->toAHardwareBuffer(),
+                                                      false, mTextureCleanupMgr);
+            }
+
+            // if the layer's buffer has a fence, then we must must respect the fence prior to using
+            // the buffer.
+            if (layer.source.buffer.fence != nullptr) {
+                waitFence(grContext, layer.source.buffer.fence->get());
+            }
+
+            // isOpaque means we need to ignore the alpha in the image,
+            // replacing it with the alpha specified by the LayerSettings. See
+            // https://developer.android.com/reference/android/view/SurfaceControl.Builder#setOpaque(boolean)
+            // The proper way to do this is to use an SkColorType that ignores
+            // alpha, like kRGB_888x_SkColorType, and that is used if the
+            // incoming image is kRGBA_8888_SkColorType. However, the incoming
+            // image may be kRGBA_F16_SkColorType, for which there is no RGBX
+            // SkColorType, or kRGBA_1010102_SkColorType, for which we have
+            // kRGB_101010x_SkColorType, but it is not yet supported as a source
+            // on the GPU. (Adding both is tracked in skbug.com/12048.) In the
+            // meantime, we'll use a workaround that works unless we need to do
+            // any color conversion. The workaround requires that we pretend the
+            // image is already premultiplied, so that we do not premultiply it
+            // before applying SkBlendMode::kPlus.
+            const bool useIsOpaqueWorkaround = item.isOpaque &&
+                    (imageTextureRef->colorType() == kRGBA_1010102_SkColorType ||
+                     imageTextureRef->colorType() == kRGBA_F16_SkColorType);
+            const auto alphaType = useIsOpaqueWorkaround ? kPremul_SkAlphaType
+                    : item.isOpaque                      ? kOpaque_SkAlphaType
+                    : item.usePremultipliedAlpha         ? kPremul_SkAlphaType
+                                                         : kUnpremul_SkAlphaType;
+            sk_sp<SkImage> image = imageTextureRef->makeImage(layerDataspace, alphaType, grContext);
+
+            auto texMatrix = getSkM44(item.textureTransform).asM33();
+            // textureTansform was intended to be passed directly into a shader, so when
+            // building the total matrix with the textureTransform we need to first
+            // normalize it, then apply the textureTransform, then scale back up.
+            texMatrix.preScale(1.0f / bounds.width(), 1.0f / bounds.height());
+            texMatrix.postScale(image->width(), image->height());
+
+            SkMatrix matrix;
+            if (!texMatrix.invert(&matrix)) {
+                matrix = texMatrix;
+            }
+            // The shader does not respect the translation, so we add it to the texture
+            // transform for the SkImage. This will make sure that the correct layer contents
+            // are drawn in the correct part of the screen.
+            matrix.postTranslate(bounds.rect().fLeft, bounds.rect().fTop);
+
+            sk_sp<SkShader> shader;
+
+            if (layer.source.buffer.useTextureFiltering) {
+                shader = image->makeShader(SkTileMode::kClamp, SkTileMode::kClamp,
+                                           SkSamplingOptions(
+                                                   {SkFilterMode::kLinear, SkMipmapMode::kNone}),
+                                           &matrix);
+            } else {
+                shader = image->makeShader(SkSamplingOptions(), matrix);
+            }
+
+            if (useIsOpaqueWorkaround) {
+                shader = SkShaders::Blend(SkBlendMode::kPlus, shader,
+                                          SkShaders::Color(SkColors::kBlack,
+                                                           toSkColorSpace(layerDataspace)));
+            }
+
+            paint.setShader(createRuntimeEffectShader(
+                    RuntimeEffectShaderParameters{.shader = shader,
+                                                  .layer = layer,
+                                                  .display = display,
+                                                  .undoPremultipliedAlpha = !item.isOpaque &&
+                                                          item.usePremultipliedAlpha,
+                                                  .requiresLinearEffect = requiresLinearEffect,
+                                                  .layerDimmingRatio = dimInLinearSpace
+                                                          ? layerDimmingRatio
+                                                          : 1.f}));
+
+            // Turn on dithering when dimming beyond this (arbitrary) threshold...
+            static constexpr float kDimmingThreshold = 0.2f;
+            // ...or we're rendering an HDR layer down to an 8-bit target
+            // Most HDR standards require at least 10-bits of color depth for source content, so we
+            // can just extract the transfer function rather than dig into precise gralloc layout.
+            // Furthermore, we can assume that the only 8-bit target we support is RGBA8888.
+            const bool requiresDownsample = isHdrDataspace(layer.sourceDataspace) &&
+                    buffer->getPixelFormat() == PIXEL_FORMAT_RGBA_8888;
+            if (layerDimmingRatio <= kDimmingThreshold || requiresDownsample) {
+                paint.setDither(true);
+            }
+            paint.setAlphaf(layer.alpha);
+
+            if (imageTextureRef->colorType() == kAlpha_8_SkColorType) {
+                LOG_ALWAYS_FATAL_IF(layer.disableBlending, "Cannot disableBlending with A8");
+
+                // SysUI creates the alpha layer as a coverage layer, which is
+                // appropriate for the DPU. Use a color matrix to convert it to
+                // a mask.
+                // TODO (b/219525258): Handle input as a mask.
+                //
+                // The color matrix will convert A8 pixels with no alpha to
+                // black, as described by this vector. If the display handles
+                // the color transform, we need to invert it to find the color
+                // that will result in black after the DPU applies the transform.
+                SkV4 black{0.0f, 0.0f, 0.0f, 1.0f}; // r, g, b, a
+                if (display.colorTransform != mat4() && display.deviceHandlesColorTransform) {
+                    SkM44 colorSpaceMatrix = getSkM44(display.colorTransform);
+                    if (colorSpaceMatrix.invert(&colorSpaceMatrix)) {
+                        black = colorSpaceMatrix * black;
+                    } else {
+                        // We'll just have to use 0,0,0 as black, which should
+                        // be close to correct.
+                        ALOGI("Could not invert colorTransform!");
+                    }
+                }
+                SkColorMatrix colorMatrix(0, 0, 0, 0, black[0],
+                                          0, 0, 0, 0, black[1],
+                                          0, 0, 0, 0, black[2],
+                                          0, 0, 0, -1, 1);
+                if (display.colorTransform != mat4() && !display.deviceHandlesColorTransform) {
+                    // On the other hand, if the device doesn't handle it, we
+                    // have to apply it ourselves.
+                    colorMatrix.postConcat(toSkColorMatrix(display.colorTransform));
+                }
+                paint.setColorFilter(SkColorFilters::Matrix(colorMatrix));
+            }
+        } else {
+            ATRACE_NAME("DrawColor");
+            const auto color = layer.source.solidColor;
+            sk_sp<SkShader> shader = SkShaders::Color(SkColor4f{.fR = color.r,
+                                                                .fG = color.g,
+                                                                .fB = color.b,
+                                                                .fA = layer.alpha},
+                                                      toSkColorSpace(layerDataspace));
+            paint.setShader(createRuntimeEffectShader(
+                    RuntimeEffectShaderParameters{.shader = shader,
+                                                  .layer = layer,
+                                                  .display = display,
+                                                  .undoPremultipliedAlpha = false,
+                                                  .requiresLinearEffect = requiresLinearEffect,
+                                                  .layerDimmingRatio = layerDimmingRatio}));
+        }
+
+        if (layer.disableBlending) {
+            paint.setBlendMode(SkBlendMode::kSrc);
+        }
+
+        // An A8 buffer will already have the proper color filter attached to
+        // its paint, including the displayColorTransform as needed.
+        if (!paint.getColorFilter()) {
+            if (!dimInLinearSpace && !equalsWithinMargin(1.0, layerDimmingRatio)) {
+                // If we don't dim in linear space, then when we gamma correct the dimming ratio we
+                // can assume a gamma 2.2 transfer function.
+                static constexpr float kInverseGamma22 = 1.f / 2.2f;
+                const auto gammaCorrectedDimmingRatio =
+                        std::pow(layerDimmingRatio, kInverseGamma22);
+                auto dimmingMatrix =
+                        mat4::scale(vec4(gammaCorrectedDimmingRatio, gammaCorrectedDimmingRatio,
+                                         gammaCorrectedDimmingRatio, 1.f));
+
+                const auto colorFilter =
+                        SkColorFilters::Matrix(toSkColorMatrix(std::move(dimmingMatrix)));
+                paint.setColorFilter(displayColorTransform
+                                             ? displayColorTransform->makeComposed(colorFilter)
+                                             : colorFilter);
+            } else {
+                paint.setColorFilter(displayColorTransform);
+            }
+        }
+
+        if (!roundRectClip.isEmpty()) {
+            canvas->clipRRect(roundRectClip, true);
+        }
+
+        if (!bounds.isRect()) {
+            paint.setAntiAlias(true);
+            canvas->drawRRect(bounds, paint);
+        } else {
+            canvas->drawRect(bounds.rect(), paint);
+        }
+        if (kFlushAfterEveryLayer) {
+            ATRACE_NAME("flush surface");
+            activeSurface->flush();
+        }
+    }
+    for (const auto& borderRenderInfo : display.borderInfoList) {
+        SkPaint p;
+        p.setColor(SkColor4f{borderRenderInfo.color.r, borderRenderInfo.color.g,
+                             borderRenderInfo.color.b, borderRenderInfo.color.a});
+        p.setAntiAlias(true);
+        p.setStyle(SkPaint::kStroke_Style);
+        p.setStrokeWidth(borderRenderInfo.width);
+        SkRegion sk_region;
+        SkPath path;
+
+        // Construct a final SkRegion using Regions
+        for (const auto& r : borderRenderInfo.combinedRegion) {
+            sk_region.op({r.left, r.top, r.right, r.bottom}, SkRegion::kUnion_Op);
+        }
+
+        sk_region.getBoundaryPath(&path);
+        canvas->drawPath(path, p);
+        path.close();
+    }
+
+    surfaceAutoSaveRestore.restore();
+    mCapture->endCapture();
+    {
+        ATRACE_NAME("flush surface");
+        LOG_ALWAYS_FATAL_IF(activeSurface != dstSurface);
+        activeSurface->flush();
+    }
+
+    base::unique_fd drawFence = flushAndSubmit(grContext);
+    resultPromise->set_value({NO_ERROR, std::move(drawFence)});
+    return;
+}
+
+size_t SkiaRenderEngine::getMaxTextureSize() const {
+    return mGrContext->maxTextureSize();
+}
+
+size_t SkiaRenderEngine::getMaxViewportDims() const {
+    return mGrContext->maxRenderTargetSize();
+}
+
+void SkiaRenderEngine::drawShadow(SkCanvas* canvas,
+                                  const SkRRect& casterRRect,
+                                  const ShadowSettings& settings) {
+    ATRACE_CALL();
+    const float casterZ = settings.length / 2.0f;
+    const auto flags =
+            settings.casterIsTranslucent ? kTransparentOccluder_ShadowFlag : kNone_ShadowFlag;
+
+    SkShadowUtils::DrawShadow(canvas, SkPath::RRect(casterRRect), SkPoint3::Make(0, 0, casterZ),
+                              getSkPoint3(settings.lightPos), settings.lightRadius,
+                              getSkColor(settings.ambientColor), getSkColor(settings.spotColor),
+                              flags);
+}
+
+void SkiaRenderEngine::onActiveDisplaySizeChanged(ui::Size size) {
+    // This cache multiplier was selected based on review of cache sizes relative
+    // to the screen resolution. Looking at the worst case memory needed by blur (~1.5x),
+    // shadows (~1x), and general data structures (e.g. vertex buffers) we selected this as a
+    // conservative default based on that analysis.
+    const float SURFACE_SIZE_MULTIPLIER = 3.5f * bytesPerPixel(mDefaultPixelFormat);
+    const int maxResourceBytes = size.width * size.height * SURFACE_SIZE_MULTIPLIER;
+
+    // start by resizing the current context
+    getActiveGrContext()->setResourceCacheLimit(maxResourceBytes);
+
+    // if it is possible to switch contexts then we will resize the other context
+    const bool originalProtectedState = mInProtectedContext;
+    useProtectedContext(!mInProtectedContext);
+    if (mInProtectedContext != originalProtectedState) {
+        getActiveGrContext()->setResourceCacheLimit(maxResourceBytes);
+        // reset back to the initial context that was active when this method was called
+        useProtectedContext(originalProtectedState);
+    }
+}
+
+void SkiaRenderEngine::dump(std::string& result) {
+    // Dump for the specific backend (GLES or Vk)
+    appendBackendSpecificInfoToDump(result);
+
+    // Info about protected content
+    StringAppendF(&result, "RenderEngine supports protected context: %d\n",
+                  supportsProtectedContent());
+    StringAppendF(&result, "RenderEngine is in protected context: %d\n", mInProtectedContext);
+    StringAppendF(&result, "RenderEngine shaders cached since last dump/primeCache: %d\n",
+                  mSkSLCacheMonitor.shadersCachedSinceLastCall());
+
+    std::vector<ResourcePair> cpuResourceMap = {
+            {"skia/sk_resource_cache/bitmap_", "Bitmaps"},
+            {"skia/sk_resource_cache/rrect-blur_", "Masks"},
+            {"skia/sk_resource_cache/rects-blur_", "Masks"},
+            {"skia/sk_resource_cache/tessellated", "Shadows"},
+            {"skia", "Other"},
+    };
+    SkiaMemoryReporter cpuReporter(cpuResourceMap, false);
+    SkGraphics::DumpMemoryStatistics(&cpuReporter);
+    StringAppendF(&result, "Skia CPU Caches: ");
+    cpuReporter.logTotals(result);
+    cpuReporter.logOutput(result);
+
+    {
+        std::lock_guard<std::mutex> lock(mRenderingMutex);
+
+        std::vector<ResourcePair> gpuResourceMap = {
+                {"texture_renderbuffer", "Texture/RenderBuffer"},
+                {"texture", "Texture"},
+                {"gr_text_blob_cache", "Text"},
+                {"skia", "Other"},
+        };
+        SkiaMemoryReporter gpuReporter(gpuResourceMap, true);
+        mGrContext->dumpMemoryStatistics(&gpuReporter);
+        StringAppendF(&result, "Skia's GPU Caches: ");
+        gpuReporter.logTotals(result);
+        gpuReporter.logOutput(result);
+        StringAppendF(&result, "Skia's Wrapped Objects:\n");
+        gpuReporter.logOutput(result, true);
+
+        StringAppendF(&result, "RenderEngine tracked buffers: %zu\n",
+                      mGraphicBufferExternalRefs.size());
+        StringAppendF(&result, "Dumping buffer ids...\n");
+        for (const auto& [id, refCounts] : mGraphicBufferExternalRefs) {
+            StringAppendF(&result, "- 0x%" PRIx64 " - %d refs \n", id, refCounts);
+        }
+        StringAppendF(&result, "RenderEngine AHB/BackendTexture cache size: %zu\n",
+                      mTextureCache.size());
+        StringAppendF(&result, "Dumping buffer ids...\n");
+        // TODO(178539829): It would be nice to know which layer these are coming from and what
+        // the texture sizes are.
+        for (const auto& [id, unused] : mTextureCache) {
+            StringAppendF(&result, "- 0x%" PRIx64 "\n", id);
+        }
+        StringAppendF(&result, "\n");
+
+        SkiaMemoryReporter gpuProtectedReporter(gpuResourceMap, true);
+        if (mProtectedGrContext) {
+            mProtectedGrContext->dumpMemoryStatistics(&gpuProtectedReporter);
+        }
+        StringAppendF(&result, "Skia's GPU Protected Caches: ");
+        gpuProtectedReporter.logTotals(result);
+        gpuProtectedReporter.logOutput(result);
+        StringAppendF(&result, "Skia's Protected Wrapped Objects:\n");
+        gpuProtectedReporter.logOutput(result, true);
+
+        StringAppendF(&result, "\n");
+        StringAppendF(&result, "RenderEngine runtime effects: %zu\n", mRuntimeEffects.size());
+        for (const auto& [linearEffect, unused] : mRuntimeEffects) {
+            StringAppendF(&result, "- inputDataspace: %s\n",
+                          dataspaceDetails(
+                                  static_cast<android_dataspace>(linearEffect.inputDataspace))
+                                  .c_str());
+            StringAppendF(&result, "- outputDataspace: %s\n",
+                          dataspaceDetails(
+                                  static_cast<android_dataspace>(linearEffect.outputDataspace))
+                                  .c_str());
+            StringAppendF(&result, "undoPremultipliedAlpha: %s\n",
+                          linearEffect.undoPremultipliedAlpha ? "true" : "false");
+        }
+    }
+    StringAppendF(&result, "\n");
+}
+
 } // namespace skia
 } // namespace renderengine
 } // namespace android
diff --git a/libs/renderengine/skia/SkiaRenderEngine.h b/libs/renderengine/skia/SkiaRenderEngine.h
index 160a186..a5cd278 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.h
+++ b/libs/renderengine/skia/SkiaRenderEngine.h
@@ -20,6 +20,31 @@
 #include <renderengine/RenderEngine.h>
 #include <sys/types.h>
 
+#include <GrBackendSemaphore.h>
+#include <GrDirectContext.h>
+#include <SkSurface.h>
+#include <android-base/thread_annotations.h>
+#include <renderengine/ExternalTexture.h>
+#include <renderengine/RenderEngine.h>
+#include <sys/types.h>
+
+#include <mutex>
+#include <unordered_map>
+
+#include "AutoBackendTexture.h"
+#include "GrContextOptions.h"
+#include "SkImageInfo.h"
+#include "SkiaRenderEngine.h"
+#include "android-base/macros.h"
+#include "debug/SkiaCapture.h"
+#include "filters/BlurFilter.h"
+#include "filters/LinearEffect.h"
+#include "filters/StretchShaderFactory.h"
+
+class SkData;
+
+struct SkPoint3;
+
 namespace android {
 
 namespace renderengine {
@@ -31,35 +56,141 @@
 
 class BlurFilter;
 
-// TODO: Put common skia stuff here that can be shared between the GL & Vulkan backends
-// Currently mostly just handles all the no-op / missing APIs
 class SkiaRenderEngine : public RenderEngine {
 public:
     static std::unique_ptr<SkiaRenderEngine> create(const RenderEngineCreationArgs& args);
-    SkiaRenderEngine(RenderEngineType type);
-    ~SkiaRenderEngine() override {}
+    SkiaRenderEngine(RenderEngineType type,
+                     PixelFormat pixelFormat,
+                     bool useColorManagement,
+                     bool supportsBackgroundBlur);
+    ~SkiaRenderEngine() override;
 
-    virtual std::future<void> primeCache() override { return {}; };
-    virtual void genTextures(size_t /*count*/, uint32_t* /*names*/) override{};
-    virtual void deleteTextures(size_t /*count*/, uint32_t const* /*names*/) override{};
-    virtual bool isProtected() const override { return false; } // mInProtectedContext; }
-    virtual bool supportsProtectedContent() const override { return false; };
-    virtual int getContextPriority() override { return 0; }
-    virtual int reportShadersCompiled() { return 0; }
-    virtual void setEnableTracing(bool tracingEnabled) override;
+    std::future<void> primeCache() override final;
+    void cleanupPostRender() override final;
+    void cleanFramebufferCache() override final{ }
+    bool isProtected() const override final{ return mInProtectedContext; }
+    bool supportsBackgroundBlur() override final {
+        return mBlurFilter != nullptr;
+    }
+    void onActiveDisplaySizeChanged(ui::Size size) override final;
+    int reportShadersCompiled();
 
+    virtual void genTextures(size_t /*count*/, uint32_t* /*names*/) override final{};
+    virtual void deleteTextures(size_t /*count*/, uint32_t const* /*names*/) override final{};
+    virtual void setEnableTracing(bool tracingEnabled) override final;
+
+    void useProtectedContext(bool useProtectedContext) override;
+    bool supportsProtectedContent() const override {
+        return supportsProtectedContentImpl();
+    }
+    void ensureGrContextsCreated();
 protected:
-    virtual void mapExternalTextureBuffer(const sp<GraphicBuffer>& /*buffer*/,
-                                          bool /*isRenderable*/) override = 0;
-    virtual void unmapExternalTextureBuffer(const sp<GraphicBuffer>& /*buffer*/) override = 0;
+    // This is so backends can stop the generic rendering state first before
+    // cleaning up backend-specific state
+    void finishRenderingAndAbandonContext();
 
-    virtual void drawLayersInternal(
-            const std::shared_ptr<std::promise<RenderEngineResult>>&& resultPromise,
-            const DisplaySettings& display, const std::vector<LayerSettings>& layers,
-            const std::shared_ptr<ExternalTexture>& buffer, const bool useFramebufferCache,
-            base::unique_fd&& bufferFence) override {
-        resultPromise->set_value({NO_ERROR, base::unique_fd()});
+    // Functions that a given backend (GLES, Vulkan) must implement
+    using Contexts = std::pair<sk_sp<GrDirectContext>, sk_sp<GrDirectContext>>;
+    virtual Contexts createDirectContexts(const GrContextOptions& options) = 0;
+    virtual bool supportsProtectedContentImpl() const = 0;
+    virtual bool useProtectedContextImpl(GrProtected isProtected) = 0;
+    virtual void waitFence(GrDirectContext* grContext, base::borrowed_fd fenceFd) = 0;
+    virtual base::unique_fd flushAndSubmit(GrDirectContext* context) = 0;
+    virtual void appendBackendSpecificInfoToDump(std::string& result) = 0;
+
+    size_t getMaxTextureSize() const override final;
+    size_t getMaxViewportDims() const override final;
+    GrDirectContext* getActiveGrContext();
+
+    // Implements PersistentCache as a way to monitor what SkSL shaders Skia has
+    // cached.
+    class SkSLCacheMonitor : public GrContextOptions::PersistentCache {
+    public:
+        SkSLCacheMonitor() = default;
+        ~SkSLCacheMonitor() override = default;
+
+        sk_sp<SkData> load(const SkData& key) override;
+
+        void store(const SkData& key, const SkData& data, const SkString& description) override;
+
+        int shadersCachedSinceLastCall() {
+            const int shadersCachedSinceLastCall = mShadersCachedSinceLastCall;
+            mShadersCachedSinceLastCall = 0;
+            return shadersCachedSinceLastCall;
+        }
+
+        int totalShadersCompiled() const { return mTotalShadersCompiled; }
+
+    private:
+        int mShadersCachedSinceLastCall = 0;
+        int mTotalShadersCompiled = 0;
     };
+
+private:
+    void mapExternalTextureBuffer(const sp<GraphicBuffer>& buffer,
+                                  bool isRenderable) override final;
+    void unmapExternalTextureBuffer(const sp<GraphicBuffer>& buffer) override final;
+    bool canSkipPostRenderCleanup() const override final;
+
+    void initCanvas(SkCanvas* canvas, const DisplaySettings& display);
+    void drawShadow(SkCanvas* canvas, const SkRRect& casterRRect,
+                    const ShadowSettings& shadowSettings);
+    void drawLayersInternal(const std::shared_ptr<std::promise<RenderEngineResult>>&& resultPromise,
+                            const DisplaySettings& display,
+                            const std::vector<LayerSettings>& layers,
+                            const std::shared_ptr<ExternalTexture>& buffer,
+                            const bool useFramebufferCache,
+                            base::unique_fd&& bufferFence) override final;
+
+    void dump(std::string& result) override final;
+
+    // If requiresLinearEffect is true or the layer has a stretchEffect a new shader is returned.
+    // Otherwise it returns the input shader.
+    struct RuntimeEffectShaderParameters {
+        sk_sp<SkShader> shader;
+        const LayerSettings& layer;
+        const DisplaySettings& display;
+        bool undoPremultipliedAlpha;
+        bool requiresLinearEffect;
+        float layerDimmingRatio;
+    };
+    sk_sp<SkShader> createRuntimeEffectShader(const RuntimeEffectShaderParameters&);
+
+    const PixelFormat mDefaultPixelFormat;
+    const bool mUseColorManagement;
+
+    // Identifier used for various mappings of layers to various
+    // textures or shaders
+    using GraphicBufferId = uint64_t;
+
+    // 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, shared between GPU contexts.
+    std::unordered_map<GraphicBufferId, std::shared_ptr<AutoBackendTexture::LocalRef>> mTextureCache
+            GUARDED_BY(mRenderingMutex);
+    std::unordered_map<shaders::LinearEffect, sk_sp<SkRuntimeEffect>, shaders::LinearEffectHasher>
+            mRuntimeEffects;
+    AutoBackendTexture::CleanupManager mTextureCleanupMgr GUARDED_BY(mRenderingMutex);
+
+    StretchShaderFactory mStretchShaderFactory;
+
+    sp<Fence> mLastDrawFence;
+    BlurFilter* mBlurFilter = nullptr;
+
+    // Object to capture commands send to Skia.
+    std::unique_ptr<SkiaCapture> mCapture;
+
+    // Mutex guarding rendering operations, so that internal state related to
+    // rendering that is potentially modified by multiple threads is guaranteed thread-safe.
+    mutable std::mutex mRenderingMutex;
+    SkSLCacheMonitor mSkSLCacheMonitor;
+
+    // Graphics context used for creating surfaces and submitting commands
+    sk_sp<GrDirectContext> mGrContext;
+    // Same as above, but for protected content (eg. DRM)
+    sk_sp<GrDirectContext> mProtectedGrContext;
+    bool mInProtectedContext = false;
 };
 
 } // namespace skia
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 23c9965..79e9d4b 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -6372,6 +6372,13 @@
 
     { // acquire lock
         std::scoped_lock _l(mLock);
+
+        // Ensure that we have an entry created for all existing displays so that if a displayId has
+        // no windows, we can tell that the windows were removed from the display.
+        for (const auto& [displayId, _] : mWindowHandlesByDisplay) {
+            handlesPerDisplay[displayId];
+        }
+
         mDisplayInfos.clear();
         for (const auto& displayInfo : displayInfos) {
             mDisplayInfos.emplace(displayInfo.displayId, displayInfo);
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index c1111f2..ba019f6 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -392,6 +392,10 @@
     }
 
     if (changes && resetNeeded) {
+        // If device was reset, cancel touch event and update touch spot state.
+        cancelTouch(mCurrentRawState.when, mCurrentRawState.readTime);
+        mCurrentCookedState.clear();
+        updateTouchSpots();
         // Send reset, unless this is the first time the device has been configured,
         // in which case the reader will call reset itself after all mappers are ready.
         NotifyDeviceResetArgs args(getContext()->getNextId(), when, getDeviceId());
@@ -951,6 +955,7 @@
     bool skipViewportUpdate = false;
     if (viewportChanged) {
         const bool viewportOrientationChanged = mViewport.orientation != newViewport->orientation;
+        const bool viewportDisplayIdChanged = mViewport.displayId != newViewport->displayId;
         mViewport = *newViewport;
 
         if (mDeviceMode == DeviceMode::DIRECT || mDeviceMode == DeviceMode::POINTER) {
@@ -1042,6 +1047,8 @@
             mDisplayHeight = rawHeight;
             mInputDeviceOrientation = DISPLAY_ORIENTATION_0;
         }
+        // If displayId changed, do not skip viewport update.
+        skipViewportUpdate &= !viewportDisplayIdChanged;
     }
 
     // If moving between pointer modes, need to reset some state.
@@ -1960,6 +1967,10 @@
 }
 
 void TouchInputMapper::abortTouches(nsecs_t when, nsecs_t readTime, uint32_t policyFlags) {
+    if (mCurrentMotionAborted) {
+        // Current motion event was already aborted.
+        return;
+    }
     BitSet32 currentIdBits = mCurrentCookedState.cookedPointerData.touchingIdBits;
     if (!currentIdBits.isEmpty()) {
         int32_t metaState = getContext()->getGlobalMetaState();
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index b2148ef..7f21dea 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -2370,6 +2370,35 @@
     thirdWindow->consumeMotionDown();
 }
 
+TEST_F(InputDispatcherTest, OnWindowInfosChanged_RemoveAllWindowsOnDisplay) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT);
+    window->setFocusable(true);
+
+    mDispatcher->onWindowInfosChanged({*window->getInfo()}, {});
+    setFocusedWindow(window);
+
+    window->consumeFocusEvent(true);
+
+    NotifyKeyArgs keyDown = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT);
+    NotifyKeyArgs keyUp = generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT);
+    mDispatcher->notifyKey(&keyDown);
+    mDispatcher->notifyKey(&keyUp);
+
+    window->consumeKeyDown(ADISPLAY_ID_DEFAULT);
+    window->consumeKeyUp(ADISPLAY_ID_DEFAULT);
+
+    // All windows are removed from the display. Ensure that we can no longer dispatch to it.
+    mDispatcher->onWindowInfosChanged({}, {});
+
+    window->consumeFocusEvent(false);
+
+    mDispatcher->notifyKey(&keyDown);
+    mDispatcher->notifyKey(&keyUp);
+    window->assertNoEvents();
+}
+
 /**
  * Ensure the correct coordinate spaces are used by InputDispatcher.
  *
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index 67fcc40..dcd8521 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -6696,6 +6696,36 @@
             toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0));
 }
 
+TEST_F(SingleTouchInputMapperTest,
+       Process_WhenViewportDisplayIdChanged_TouchIsCanceledAndDeviceIsReset) {
+    addConfigurationProperty("touch.deviceType", "touchScreen");
+    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareButtons();
+    prepareAxes(POSITION);
+    SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+    NotifyMotionArgs motionArgs;
+
+    // Down.
+    int32_t x = 100;
+    int32_t y = 200;
+    processDown(mapper, x, y);
+    processSync(mapper);
+
+    // We should receive a down event
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
+
+    // Change display id
+    clearViewports();
+    prepareSecondaryDisplay(ViewportType::INTERNAL);
+
+    // We should receive a cancel event
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(AMOTION_EVENT_ACTION_CANCEL, motionArgs.action);
+    // Then receive reset called
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled());
+}
+
 // --- TouchDisplayProjectionTest ---
 
 class TouchDisplayProjectionTest : public SingleTouchInputMapperTest {
@@ -8739,6 +8769,10 @@
         // window's coordinate space.
         frames[0].rotate(getInverseRotation(orientation));
         ASSERT_EQ(frames, motionArgs.videoFrames);
+
+        // Release finger.
+        processSync(mapper);
+        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     }
 }
 
diff --git a/services/powermanager/tests/IThermalManagerTest.cpp b/services/powermanager/tests/IThermalManagerTest.cpp
index b62be5f..7414958 100644
--- a/services/powermanager/tests/IThermalManagerTest.cpp
+++ b/services/powermanager/tests/IThermalManagerTest.cpp
@@ -33,26 +33,38 @@
 using namespace android::os;
 using namespace std::chrono_literals;
 
-class IThermalServiceTest : public testing::Test,
-                            public BnThermalStatusListener{
+class IThermalServiceTestListener : public BnThermalStatusListener {
+    public:
+        virtual binder::Status onStatusChange(int status) override;
+        std::condition_variable mCondition;
+        int mListenerStatus = 0;
+        std::mutex mMutex;
+};
+
+binder::Status IThermalServiceTestListener::onStatusChange(int status) {
+    std::unique_lock<std::mutex> lock(mMutex);
+    mListenerStatus = status;
+    ALOGI("IThermalServiceTestListener::notifyListener %d", mListenerStatus);
+    mCondition.notify_all();
+    return binder::Status::ok();
+}
+
+class IThermalServiceTest : public testing::Test {
     public:
         IThermalServiceTest();
         void setThermalOverride(int level);
-        virtual binder::Status onStatusChange(int status) override;
         int getStatusFromService();
         void SetUp() override;
         void TearDown() override;
     protected:
         sp<IThermalService> mThermalSvc;
-        std::condition_variable mCondition;
-        int mListenerStatus;
         int mServiceStatus;
-        std::mutex mMutex;
+        sp<IThermalServiceTestListener> mCallback;
 };
 
 IThermalServiceTest::IThermalServiceTest()
- : mListenerStatus(0),
-   mServiceStatus(0) {
+ : mServiceStatus(0),
+   mCallback(sp<IThermalServiceTestListener>::make()) {
 }
 
 void IThermalServiceTest::setThermalOverride(int level) {
@@ -60,14 +72,6 @@
     system(cmdStr.c_str());
 }
 
-binder::Status IThermalServiceTest::onStatusChange(int status) {
-    std::unique_lock<std::mutex> lock(mMutex);
-    mListenerStatus = status;
-    ALOGI("IThermalServiceTest::notifyListener %d", mListenerStatus);
-    mCondition.notify_all();
-    return binder::Status::ok();
-}
-
 int IThermalServiceTest::getStatusFromService() {
     int status;
     binder::Status ret = mThermalSvc->getCurrentThermalStatus(&status);
@@ -87,19 +91,19 @@
     mThermalSvc = interface_cast<IThermalService>(binder);
     EXPECT_NE(mThermalSvc, nullptr);
     // Lock mutex for operation, so listener will only be processed after wait_for is called
-    std::unique_lock<std::mutex> lock(mMutex);
+    std::unique_lock<std::mutex> lock(mCallback->mMutex);
     bool success = false;
-    binder::Status ret = mThermalSvc->registerThermalStatusListener(this, &success);
+    binder::Status ret = mThermalSvc->registerThermalStatusListener(mCallback, &success);
     // Check the result
     ASSERT_TRUE(success);
     ASSERT_TRUE(ret.isOk());
     // Wait for listener called after registration, shouldn't timeout
-    EXPECT_NE(mCondition.wait_for(lock, 1s), std::cv_status::timeout);
+    EXPECT_NE(mCallback->mCondition.wait_for(lock, 1s), std::cv_status::timeout);
 }
 
 void IThermalServiceTest::TearDown() {
     bool success = false;
-    binder::Status ret = mThermalSvc->unregisterThermalStatusListener(this, &success);
+    binder::Status ret = mThermalSvc->unregisterThermalStatusListener(mCallback, &success);
     ASSERT_TRUE(success);
     ASSERT_TRUE(ret.isOk());
 }
@@ -114,14 +118,14 @@
 TEST_P(IThermalListenerTest, TestListener) {
     int level = GetParam();
     // Lock mutex for operation, so listener will only be processed after wait_for is called
-    std::unique_lock<std::mutex> lock(mMutex);
+    std::unique_lock<std::mutex> lock(mCallback->mMutex);
     // Set the override thermal status
     setThermalOverride(level);
     // Wait for listener called, shouldn't timeout
-    EXPECT_NE(mCondition.wait_for(lock, 1s), std::cv_status::timeout);
+    EXPECT_NE(mCallback->mCondition.wait_for(lock, 1s), std::cv_status::timeout);
     // Check the result
-    EXPECT_EQ(level, mListenerStatus);
-    ALOGI("Thermal listener status %d, expecting %d", mListenerStatus, level);
+    EXPECT_EQ(level, mCallback->mListenerStatus);
+    ALOGI("Thermal listener status %d, expecting %d", mCallback->mListenerStatus, level);
 }
 
 INSTANTIATE_TEST_SUITE_P(TestListenerLevels, IThermalListenerTest, testing::Range(
@@ -158,4 +162,4 @@
     ALOGV("Test result = %d\n", status);
 
     return status;
-}
\ No newline at end of file
+}
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index f1457bf..a8ba15f 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -163,6 +163,7 @@
         "FrameTracer/FrameTracer.cpp",
         "FrameTracker.cpp",
         "HdrLayerInfoReporter.cpp",
+        "HwcSlotGenerator.cpp",
         "WindowInfosListenerInvoker.cpp",
         "Layer.cpp",
         "LayerProtoHelper.cpp",
diff --git a/services/surfaceflinger/BufferStateLayer.cpp b/services/surfaceflinger/BufferStateLayer.cpp
index 59e2d18..5656e05 100644
--- a/services/surfaceflinger/BufferStateLayer.cpp
+++ b/services/surfaceflinger/BufferStateLayer.cpp
@@ -25,7 +25,6 @@
 
 #include <FrameTimeline/FrameTimeline.h>
 #include <compositionengine/CompositionEngine.h>
-#include <compositionengine/LayerFECompositionState.h>
 #include <gui/BufferQueue.h>
 #include <private/gui/SyncFeatures.h>
 #include <renderengine/Image.h>
@@ -71,22 +70,64 @@
 
 using PresentState = frametimeline::SurfaceFrame::PresentState;
 using gui::WindowInfo;
-void BufferStateLayer::callReleaseBufferCallback(const sp<ITransactionCompletedListener>& listener,
-                                                 const sp<GraphicBuffer>& buffer,
-                                                 uint64_t framenumber,
-                                                 const sp<Fence>& releaseFence,
-                                                 uint32_t currentMaxAcquiredBufferCount) {
-    if (!listener) {
-        return;
-    }
-    ATRACE_FORMAT_INSTANT("callReleaseBufferCallback %s - %" PRIu64, getDebugName(), framenumber);
-    listener->onReleaseBuffer({buffer->getId(), framenumber},
-                              releaseFence ? releaseFence : Fence::NO_FENCE,
-                              currentMaxAcquiredBufferCount);
-}
-
 namespace {
 static constexpr float defaultMaxLuminance = 1000.0;
+
+constexpr mat4 inverseOrientation(uint32_t transform) {
+    const mat4 flipH(-1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1);
+    const mat4 flipV(1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1);
+    const mat4 rot90(0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1);
+    mat4 tr;
+
+    if (transform & NATIVE_WINDOW_TRANSFORM_ROT_90) {
+        tr = tr * rot90;
+    }
+    if (transform & NATIVE_WINDOW_TRANSFORM_FLIP_H) {
+        tr = tr * flipH;
+    }
+    if (transform & NATIVE_WINDOW_TRANSFORM_FLIP_V) {
+        tr = tr * flipV;
+    }
+    return inverse(tr);
+}
+
+bool assignTransform(ui::Transform* dst, ui::Transform& from) {
+    if (*dst == from) {
+        return false;
+    }
+    *dst = from;
+    return true;
+}
+
+TimeStats::SetFrameRateVote frameRateToSetFrameRateVotePayload(Layer::FrameRate frameRate) {
+    using FrameRateCompatibility = TimeStats::SetFrameRateVote::FrameRateCompatibility;
+    using Seamlessness = TimeStats::SetFrameRateVote::Seamlessness;
+    const auto frameRateCompatibility = [frameRate] {
+        switch (frameRate.type) {
+            case Layer::FrameRateCompatibility::Default:
+                return FrameRateCompatibility::Default;
+            case Layer::FrameRateCompatibility::ExactOrMultiple:
+                return FrameRateCompatibility::ExactOrMultiple;
+            default:
+                return FrameRateCompatibility::Undefined;
+        }
+    }();
+
+    const auto seamlessness = [frameRate] {
+        switch (frameRate.seamlessness) {
+            case scheduler::Seamlessness::OnlySeamless:
+                return Seamlessness::ShouldBeSeamless;
+            case scheduler::Seamlessness::SeamedAndSeamless:
+                return Seamlessness::NotRequired;
+            default:
+                return Seamlessness::Undefined;
+        }
+    }();
+
+    return TimeStats::SetFrameRateVote{.frameRate = frameRate.rate.getValue(),
+                                       .frameRateCompatibility = frameRateCompatibility,
+                                       .seamlessness = seamlessness};
+}
 } // namespace
 
 BufferStateLayer::BufferStateLayer(const LayerCreationArgs& args)
@@ -97,7 +138,6 @@
     ALOGV("Creating Layer %s", getDebugName());
 
     mPremultipliedAlpha = !(args.flags & ISurfaceComposerClient::eNonPremultiplied);
-
     mPotentialCursor = args.flags & ISurfaceComposerClient::eCursorWindow;
     mProtectedByApp = args.flags & ISurfaceComposerClient::eProtectedByApp;
     mDrawingState.dataspace = ui::Dataspace::V0_SRGB;
@@ -127,6 +167,20 @@
     mFlinger->mFrameTracer->onDestroy(layerId);
 }
 
+void BufferStateLayer::callReleaseBufferCallback(const sp<ITransactionCompletedListener>& listener,
+                                                 const sp<GraphicBuffer>& buffer,
+                                                 uint64_t framenumber,
+                                                 const sp<Fence>& releaseFence,
+                                                 uint32_t currentMaxAcquiredBufferCount) {
+    if (!listener) {
+        return;
+    }
+    ATRACE_FORMAT_INSTANT("callReleaseBufferCallback %s - %" PRIu64, getDebugName(), framenumber);
+    listener->onReleaseBuffer({buffer->getId(), framenumber},
+                              releaseFence ? releaseFence : Fence::NO_FENCE,
+                              currentMaxAcquiredBufferCount);
+}
+
 // -----------------------------------------------------------------------
 // Interface implementation for Layer
 // -----------------------------------------------------------------------
@@ -239,14 +293,6 @@
     mDrawingState.callbackHandles = {};
 }
 
-void BufferStateLayer::finalizeFrameEventHistory(const std::shared_ptr<FenceTime>& glDoneFence,
-                                                 const CompositorTiming& compositorTiming) {
-    for (const auto& handle : mDrawingState.callbackHandles) {
-        handle->gpuCompositionDoneFence = glDoneFence;
-        handle->compositorTiming = compositorTiming;
-    }
-}
-
 bool BufferStateLayer::willPresentCurrentTransaction() const {
     // Returns true if the most recent Transaction applied to CurrentState will be presented.
     return (getSidebandStreamChanged() || getAutoRefresh() ||
@@ -307,14 +353,6 @@
     return true;
 }
 
-static bool assignTransform(ui::Transform* dst, ui::Transform& from) {
-    if (*dst == from) {
-        return false;
-    }
-    *dst = from;
-    return true;
-}
-
 // Translate destination frame into scale and position. If a destination frame is not set, use the
 // provided scale and position
 bool BufferStateLayer::updateGeometry() {
@@ -641,14 +679,6 @@
     return fenceSignaled;
 }
 
-bool BufferStateLayer::framePresentTimeIsCurrent(nsecs_t expectedPresentTime) const {
-    if (!hasFrameUpdate() || isRemovedFromCurrentState()) {
-        return true;
-    }
-
-    return mDrawingState.isAutoTimestamp || mDrawingState.desiredPresentTime <= expectedPresentTime;
-}
-
 bool BufferStateLayer::onPreComposition(nsecs_t refreshStartTime) {
     for (const auto& handle : mDrawingState.callbackHandles) {
         handle->refreshStartTime = refreshStartTime;
@@ -685,8 +715,7 @@
     return (mDrawingStateModified || mDrawingState.modified) && (c.buffer != nullptr || c.bgColorLayer != nullptr);
 }
 
-status_t BufferStateLayer::updateTexImage(bool& /*recomputeVisibleRegions*/, nsecs_t latchTime,
-                                          nsecs_t /*expectedPresentTime*/) {
+void BufferStateLayer::updateTexImage(nsecs_t latchTime) {
     const State& s(getDrawingState());
 
     if (!s.buffer) {
@@ -695,7 +724,7 @@
                 handle->latchTime = latchTime;
             }
         }
-        return NO_ERROR;
+        return;
     }
 
     for (auto& handle : mDrawingState.callbackHandles) {
@@ -734,135 +763,36 @@
     mDrawingState.callbackHandles = remainingHandles;
 
     mDrawingStateModified = false;
-
-    return NO_ERROR;
 }
 
-status_t BufferStateLayer::updateActiveBuffer() {
-    const State& s(getDrawingState());
-
-    if (s.buffer == nullptr) {
-        return BAD_VALUE;
-    }
-
-    if (!mBufferInfo.mBuffer || !s.buffer->hasSameBuffer(*mBufferInfo.mBuffer)) {
+void BufferStateLayer::gatherBufferInfo() {
+    if (!mBufferInfo.mBuffer || !mDrawingState.buffer->hasSameBuffer(*mBufferInfo.mBuffer)) {
         decrementPendingBufferCount();
     }
 
     mPreviousReleaseCallbackId = {getCurrentBufferId(), mBufferInfo.mFrameNumber};
-    mBufferInfo.mBuffer = s.buffer;
-    mBufferInfo.mFence = s.acquireFence;
-    mBufferInfo.mFrameNumber = s.frameNumber;
-
-    return NO_ERROR;
-}
-
-status_t BufferStateLayer::updateFrameNumber() {
-    // TODO(marissaw): support frame history events
-    mPreviousFrameNumber = mCurrentFrameNumber;
-    mCurrentFrameNumber = mDrawingState.frameNumber;
-    return NO_ERROR;
-}
-
-void BufferStateLayer::HwcSlotGenerator::bufferErased(const client_cache_t& clientCacheId) {
-    std::lock_guard lock(mMutex);
-    if (!clientCacheId.isValid()) {
-        ALOGE("invalid process, failed to erase buffer");
-        return;
-    }
-    eraseBufferLocked(clientCacheId);
-}
-
-int BufferStateLayer::HwcSlotGenerator::getHwcCacheSlot(const client_cache_t& clientCacheId) {
-    std::lock_guard<std::mutex> lock(mMutex);
-    auto itr = mCachedBuffers.find(clientCacheId);
-    if (itr == mCachedBuffers.end()) {
-        return addCachedBuffer(clientCacheId);
-    }
-    auto& [hwcCacheSlot, counter] = itr->second;
-    counter = mCounter++;
-    return hwcCacheSlot;
-}
-
-int BufferStateLayer::HwcSlotGenerator::addCachedBuffer(const client_cache_t& clientCacheId)
-        REQUIRES(mMutex) {
-    if (!clientCacheId.isValid()) {
-        ALOGE("invalid process, returning invalid slot");
-        return BufferQueue::INVALID_BUFFER_SLOT;
-    }
-
-    ClientCache::getInstance().registerErasedRecipient(clientCacheId, wp<ErasedRecipient>(this));
-
-    int hwcCacheSlot = getFreeHwcCacheSlot();
-    mCachedBuffers[clientCacheId] = {hwcCacheSlot, mCounter++};
-    return hwcCacheSlot;
-}
-
-int BufferStateLayer::HwcSlotGenerator::getFreeHwcCacheSlot() REQUIRES(mMutex) {
-    if (mFreeHwcCacheSlots.empty()) {
-        evictLeastRecentlyUsed();
-    }
-
-    int hwcCacheSlot = mFreeHwcCacheSlots.top();
-    mFreeHwcCacheSlots.pop();
-    return hwcCacheSlot;
-}
-
-void BufferStateLayer::HwcSlotGenerator::evictLeastRecentlyUsed() REQUIRES(mMutex) {
-    uint64_t minCounter = UINT_MAX;
-    client_cache_t minClientCacheId = {};
-    for (const auto& [clientCacheId, slotCounter] : mCachedBuffers) {
-        const auto& [hwcCacheSlot, counter] = slotCounter;
-        if (counter < minCounter) {
-            minCounter = counter;
-            minClientCacheId = clientCacheId;
-        }
-    }
-    eraseBufferLocked(minClientCacheId);
-
-    ClientCache::getInstance().unregisterErasedRecipient(minClientCacheId, this);
-}
-
-void BufferStateLayer::HwcSlotGenerator::eraseBufferLocked(const client_cache_t& clientCacheId)
-        REQUIRES(mMutex) {
-    auto itr = mCachedBuffers.find(clientCacheId);
-    if (itr == mCachedBuffers.end()) {
-        return;
-    }
-    auto& [hwcCacheSlot, counter] = itr->second;
-
-    // TODO send to hwc cache and resources
-
-    mFreeHwcCacheSlots.push(hwcCacheSlot);
-    mCachedBuffers.erase(clientCacheId);
-}
-
-void BufferStateLayer::gatherBufferInfo() {
+    mBufferInfo.mBuffer = mDrawingState.buffer;
+    mBufferInfo.mFence = mDrawingState.acquireFence;
+    mBufferInfo.mFrameNumber = mDrawingState.frameNumber;
     mBufferInfo.mPixelFormat =
             !mBufferInfo.mBuffer ? PIXEL_FORMAT_NONE : mBufferInfo.mBuffer->getPixelFormat();
     mBufferInfo.mFrameLatencyNeeded = true;
-
-    const State& s(getDrawingState());
-    mBufferInfo.mDesiredPresentTime = s.desiredPresentTime;
-    mBufferInfo.mFenceTime = std::make_shared<FenceTime>(s.acquireFence);
-    mBufferInfo.mFence = s.acquireFence;
-    mBufferInfo.mTransform = s.bufferTransform;
+    mBufferInfo.mDesiredPresentTime = mDrawingState.desiredPresentTime;
+    mBufferInfo.mFenceTime = std::make_shared<FenceTime>(mDrawingState.acquireFence);
+    mBufferInfo.mFence = mDrawingState.acquireFence;
+    mBufferInfo.mTransform = mDrawingState.bufferTransform;
     auto lastDataspace = mBufferInfo.mDataspace;
-    mBufferInfo.mDataspace = translateDataspace(s.dataspace);
+    mBufferInfo.mDataspace = translateDataspace(mDrawingState.dataspace);
     if (lastDataspace != mBufferInfo.mDataspace) {
         mFlinger->mSomeDataspaceChanged = true;
     }
-    mBufferInfo.mCrop = computeBufferCrop(s);
+    mBufferInfo.mCrop = computeBufferCrop(mDrawingState);
     mBufferInfo.mScaleMode = NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW;
-    mBufferInfo.mSurfaceDamage = s.surfaceDamageRegion;
-    mBufferInfo.mHdrMetadata = s.hdrMetadata;
-    mBufferInfo.mApi = s.api;
-    mBufferInfo.mTransformToDisplayInverse = s.transformToDisplayInverse;
-    mBufferInfo.mBufferSlot = mHwcSlotGenerator->getHwcCacheSlot(s.clientCacheId);
-}
-
-uint32_t BufferStateLayer::getEffectiveScalingMode() const {
-   return NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW;
+    mBufferInfo.mSurfaceDamage = mDrawingState.surfaceDamageRegion;
+    mBufferInfo.mHdrMetadata = mDrawingState.hdrMetadata;
+    mBufferInfo.mApi = mDrawingState.api;
+    mBufferInfo.mTransformToDisplayInverse = mDrawingState.transformToDisplayInverse;
+    mBufferInfo.mBufferSlot = mHwcSlotGenerator->getHwcCacheSlot(mDrawingState.clientCacheId);
 }
 
 Rect BufferStateLayer::computeBufferCrop(const State& s) {
@@ -1178,32 +1108,6 @@
             (mBufferInfo.mBuffer != nullptr || mSidebandStream != nullptr);
 }
 
-bool BufferStateLayer::isFixedSize() const {
-    return true;
-}
-
-bool BufferStateLayer::usesSourceCrop() const {
-    return true;
-}
-
-static constexpr mat4 inverseOrientation(uint32_t transform) {
-    const mat4 flipH(-1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1);
-    const mat4 flipV(1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1);
-    const mat4 rot90(0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1);
-    mat4 tr;
-
-    if (transform & NATIVE_WINDOW_TRANSFORM_ROT_90) {
-        tr = tr * rot90;
-    }
-    if (transform & NATIVE_WINDOW_TRANSFORM_FLIP_H) {
-        tr = tr * flipH;
-    }
-    if (transform & NATIVE_WINDOW_TRANSFORM_FLIP_V) {
-        tr = tr * flipV;
-    }
-    return inverse(tr);
-}
-
 std::optional<compositionengine::LayerFE::LayerSettings> BufferStateLayer::prepareClientComposition(
         compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) {
     ATRACE_CALL();
@@ -1373,38 +1277,6 @@
     compositionState->sidebandStreamHasFrame = false;
 }
 
-namespace {
-TimeStats::SetFrameRateVote frameRateToSetFrameRateVotePayload(Layer::FrameRate frameRate) {
-    using FrameRateCompatibility = TimeStats::SetFrameRateVote::FrameRateCompatibility;
-    using Seamlessness = TimeStats::SetFrameRateVote::Seamlessness;
-    const auto frameRateCompatibility = [frameRate] {
-        switch (frameRate.type) {
-            case Layer::FrameRateCompatibility::Default:
-                return FrameRateCompatibility::Default;
-            case Layer::FrameRateCompatibility::ExactOrMultiple:
-                return FrameRateCompatibility::ExactOrMultiple;
-            default:
-                return FrameRateCompatibility::Undefined;
-        }
-    }();
-
-    const auto seamlessness = [frameRate] {
-        switch (frameRate.seamlessness) {
-            case scheduler::Seamlessness::OnlySeamless:
-                return Seamlessness::ShouldBeSeamless;
-            case scheduler::Seamlessness::SeamedAndSeamless:
-                return Seamlessness::NotRequired;
-            default:
-                return Seamlessness::Undefined;
-        }
-    }();
-
-    return TimeStats::SetFrameRateVote{.frameRate = frameRate.rate.getValue(),
-                                       .frameRateCompatibility = frameRateCompatibility,
-                                       .seamlessness = seamlessness};
-}
-} // namespace
-
 void BufferStateLayer::onPostComposition(const DisplayDevice* display,
                                          const std::shared_ptr<FenceTime>& glDoneFence,
                                          const std::shared_ptr<FenceTime>& presentFence,
@@ -1413,8 +1285,10 @@
     // composition.
     if (!mBufferInfo.mFrameLatencyNeeded) return;
 
-    // Update mFrameEventHistory.
-    finalizeFrameEventHistory(glDoneFence, compositorTiming);
+    for (const auto& handle : mDrawingState.callbackHandles) {
+        handle->gpuCompositionDoneFence = glDoneFence;
+        handle->compositorTiming = compositorTiming;
+    }
 
     // Update mFrameTracker.
     nsecs_t desiredPresentTime = mBufferInfo.mDesiredPresentTime;
@@ -1480,8 +1354,7 @@
     mBufferInfo.mFrameLatencyNeeded = false;
 }
 
-bool BufferStateLayer::latchBuffer(bool& recomputeVisibleRegions, nsecs_t latchTime,
-                                   nsecs_t expectedPresentTime) {
+bool BufferStateLayer::latchBuffer(bool& recomputeVisibleRegions, nsecs_t latchTime) {
     ATRACE_FORMAT_INSTANT("latchBuffer %s - %" PRIu64, getDebugName(),
                           getDrawingState().frameNumber);
 
@@ -1499,27 +1372,16 @@
         return false;
     }
 
+    updateTexImage(latchTime);
+    if (mDrawingState.buffer == nullptr) {
+        return false;
+    }
+
     // Capture the old state of the layer for comparisons later
-    const State& s(getDrawingState());
-    const bool oldOpacity = isOpaque(s);
-
     BufferInfo oldBufferInfo = mBufferInfo;
-
-    status_t err = updateTexImage(recomputeVisibleRegions, latchTime, expectedPresentTime);
-    if (err != NO_ERROR) {
-        return false;
-    }
-
-    err = updateActiveBuffer();
-    if (err != NO_ERROR) {
-        return false;
-    }
-
-    err = updateFrameNumber();
-    if (err != NO_ERROR) {
-        return false;
-    }
-
+    const bool oldOpacity = isOpaque(mDrawingState);
+    mPreviousFrameNumber = mCurrentFrameNumber;
+    mCurrentFrameNumber = mDrawingState.frameNumber;
     gatherBufferInfo();
 
     if (oldBufferInfo.mBuffer == nullptr) {
@@ -1544,7 +1406,7 @@
         }
     }
 
-    if (oldOpacity != isOpaque(s)) {
+    if (oldOpacity != isOpaque(mDrawingState)) {
         recomputeVisibleRegions = true;
     }
 
@@ -1628,7 +1490,7 @@
 void BufferStateLayer::latchAndReleaseBuffer() {
     if (hasReadyFrame()) {
         bool ignored = false;
-        latchBuffer(ignored, systemTime(), 0 /* expectedPresentTime */);
+        latchBuffer(ignored, systemTime());
     }
     releasePendingBuffer(systemTime());
 }
diff --git a/services/surfaceflinger/BufferStateLayer.h b/services/surfaceflinger/BufferStateLayer.h
index df7ae61..8bad3d2 100644
--- a/services/surfaceflinger/BufferStateLayer.h
+++ b/services/surfaceflinger/BufferStateLayer.h
@@ -40,6 +40,7 @@
 #include "DisplayHardware/HWComposer.h"
 #include "FrameTimeline.h"
 #include "FrameTracker.h"
+#include "HwcSlotGenerator.h"
 #include "Layer.h"
 #include "LayerVector.h"
 #include "SurfaceFlinger.h"
@@ -74,10 +75,7 @@
     // GRALLOC_USAGE_PROTECTED sense.
     bool isProtected() const override;
 
-    // isFixedSize - true if content has a fixed size
-    bool isFixedSize() const override;
-
-    bool usesSourceCrop() const override;
+    bool usesSourceCrop() const override { return true; }
 
     bool isHdrY410() const override;
 
@@ -89,13 +87,9 @@
     // the visible regions need to be recomputed (this is a fairly heavy
     // operation, so this should be set only if needed). Typically this is used
     // to figure out if the content or size of a surface has changed.
-    bool latchBuffer(bool& recomputeVisibleRegions, nsecs_t latchTime,
-                     nsecs_t expectedPresentTime) override;
+    bool latchBuffer(bool& recomputeVisibleRegions, nsecs_t latchTime) override;
     bool hasReadyFrame() const override;
 
-    // Returns the current scaling mode
-    uint32_t getEffectiveScalingMode() const override;
-
     // Calls latchBuffer if the buffer has a frame queued and then releases the buffer.
     // This is used if the buffer is just latched and releases to free up the buffer
     // and will not be shown on screen.
@@ -122,14 +116,6 @@
 
     void releasePendingBuffer(nsecs_t dequeueReadyTime) override;
 
-    void finalizeFrameEventHistory(const std::shared_ptr<FenceTime>& glDoneFence,
-                                   const CompositorTiming& compositorTiming) override;
-
-    // Returns true if the next buffer should be presented at the expected present time,
-    // overridden by BufferStateLayer and BufferQueueLayer for implementation
-    // specific logic
-    bool isBufferDue(nsecs_t /*expectedPresentTime*/) const { return true; }
-
     Region getActiveTransparentRegion(const Layer::State& s) const override {
         return s.transparentRegionHint;
     }
@@ -166,7 +152,6 @@
     bool updateGeometry() override;
 
     bool fenceHasSignaled() const;
-    bool framePresentTimeIsCurrent(nsecs_t expectedPresentTime) const;
     bool onPreComposition(nsecs_t) override;
 
     // See mPendingBufferTransactions
@@ -174,9 +159,6 @@
     std::atomic<int32_t>* getPendingBufferCounter() override { return &mPendingBufferTransactions; }
     std::string getPendingBufferCounterName() override { return mBlastTransactionName; }
 
-    // Returns true if the next buffer should be presented at the expected present time
-    bool shouldPresentNow(nsecs_t /*expectedPresentTime*/) const override { return true; }
-
 protected:
     void gatherBufferInfo();
     void onSurfaceFrameCreated(const std::shared_ptr<frametimeline::SurfaceFrame>& surfaceFrame);
@@ -268,11 +250,7 @@
 
     bool hasFrameUpdate() const;
 
-    status_t updateTexImage(bool& recomputeVisibleRegions, nsecs_t latchTime,
-                            nsecs_t expectedPresentTime);
-
-    status_t updateActiveBuffer();
-    status_t updateFrameNumber();
+    void updateTexImage(nsecs_t latchTime);
 
     sp<Layer> createClone() override;
 
@@ -323,45 +301,6 @@
     // not specify a destination frame.
     ui::Transform mRequestedTransform;
 
-    // TODO(marissaw): support sticky transform for LEGACY camera mode
-
-    class HwcSlotGenerator : public ClientCache::ErasedRecipient {
-    public:
-        HwcSlotGenerator() {
-            for (int i = 0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) {
-                mFreeHwcCacheSlots.push(i);
-            }
-        }
-
-        void bufferErased(const client_cache_t& clientCacheId);
-
-        int getHwcCacheSlot(const client_cache_t& clientCacheId);
-
-    private:
-        friend class SlotGenerationTest;
-        int addCachedBuffer(const client_cache_t& clientCacheId) REQUIRES(mMutex);
-        int getFreeHwcCacheSlot() REQUIRES(mMutex);
-        void evictLeastRecentlyUsed() REQUIRES(mMutex);
-        void eraseBufferLocked(const client_cache_t& clientCacheId) REQUIRES(mMutex);
-
-        struct CachedBufferHash {
-            std::size_t operator()(const client_cache_t& clientCacheId) const {
-                return std::hash<uint64_t>{}(clientCacheId.id);
-            }
-        };
-
-        std::mutex mMutex;
-
-        std::unordered_map<client_cache_t, std::pair<int /*HwcCacheSlot*/, uint64_t /*counter*/>,
-                           CachedBufferHash>
-                mCachedBuffers GUARDED_BY(mMutex);
-        std::stack<int /*HwcCacheSlot*/> mFreeHwcCacheSlots GUARDED_BY(mMutex);
-
-        // The cache increments this counter value when a slot is updated or used.
-        // Used to track the least recently-used buffer
-        uint64_t mCounter = 0;
-    };
-
     sp<HwcSlotGenerator> mHwcSlotGenerator;
 };
 
diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
index 77dda6c..62078ba 100644
--- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
+++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
@@ -187,7 +187,7 @@
     if (!mSupportsPowerHint.has_value()) {
         std::lock_guard lock(mPowerHalMutex);
         HalWrapper* const halWrapper = getPowerHal();
-        mSupportsPowerHint = halWrapper->supportsPowerHintSession();
+        mSupportsPowerHint = halWrapper && halWrapper->supportsPowerHintSession();
     }
     return *mSupportsPowerHint;
 }
@@ -759,9 +759,10 @@
 }
 
 bool AidlPowerHalWrapper::shouldReportActualDurations() {
-    // Report if we have never reported before or are approaching a stale session
+    // Report if we have never reported before or will go stale next frame
     if (!mLastActualDurationSent.has_value() ||
-        (systemTime() - mLastActualReportTimestamp) > kStaleTimeout.count()) {
+        (mLastTargetDurationSent + systemTime() - mLastActualReportTimestamp) >
+                kStaleTimeout.count()) {
         return true;
     }
 
diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
index 98921b0..a93744f 100644
--- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
+++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
@@ -332,7 +332,7 @@
     static const bool sTraceHintSessionData;
     static constexpr const std::chrono::nanoseconds kDefaultTarget = 16ms;
     // Amount of time after the last message was sent before the session goes stale
-    // actually 100ms but we use 80 here to ideally avoid going stale
+    // actually 100ms but we use 80 here to give some slack
     static constexpr const std::chrono::nanoseconds kStaleTimeout = 80ms;
 };
 
diff --git a/services/surfaceflinger/HwcSlotGenerator.cpp b/services/surfaceflinger/HwcSlotGenerator.cpp
new file mode 100644
index 0000000..b7e9607
--- /dev/null
+++ b/services/surfaceflinger/HwcSlotGenerator.cpp
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2022 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 "HwcSlotGenerator"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
+#include <gui/BufferQueue.h>
+
+#include "HwcSlotGenerator.h"
+
+namespace android {
+
+HwcSlotGenerator::HwcSlotGenerator() {
+    for (int i = 0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) {
+        mFreeHwcCacheSlots.push(i);
+    }
+}
+
+void HwcSlotGenerator::bufferErased(const client_cache_t& clientCacheId) {
+    std::lock_guard lock(mMutex);
+    if (!clientCacheId.isValid()) {
+        ALOGE("invalid process, failed to erase buffer");
+        return;
+    }
+    eraseBufferLocked(clientCacheId);
+}
+
+int HwcSlotGenerator::getHwcCacheSlot(const client_cache_t& clientCacheId) {
+    std::lock_guard<std::mutex> lock(mMutex);
+    auto itr = mCachedBuffers.find(clientCacheId);
+    if (itr == mCachedBuffers.end()) {
+        return addCachedBuffer(clientCacheId);
+    }
+    auto& [hwcCacheSlot, counter] = itr->second;
+    counter = mCounter++;
+    return hwcCacheSlot;
+}
+
+int HwcSlotGenerator::addCachedBuffer(const client_cache_t& clientCacheId) REQUIRES(mMutex) {
+    if (!clientCacheId.isValid()) {
+        ALOGE("invalid process, returning invalid slot");
+        return BufferQueue::INVALID_BUFFER_SLOT;
+    }
+
+    ClientCache::getInstance().registerErasedRecipient(clientCacheId, wp<ErasedRecipient>(this));
+
+    int hwcCacheSlot = getFreeHwcCacheSlot();
+    mCachedBuffers[clientCacheId] = {hwcCacheSlot, mCounter++};
+    return hwcCacheSlot;
+}
+
+int HwcSlotGenerator::getFreeHwcCacheSlot() REQUIRES(mMutex) {
+    if (mFreeHwcCacheSlots.empty()) {
+        evictLeastRecentlyUsed();
+    }
+
+    int hwcCacheSlot = mFreeHwcCacheSlots.top();
+    mFreeHwcCacheSlots.pop();
+    return hwcCacheSlot;
+}
+
+void HwcSlotGenerator::evictLeastRecentlyUsed() REQUIRES(mMutex) {
+    uint64_t minCounter = UINT_MAX;
+    client_cache_t minClientCacheId = {};
+    for (const auto& [clientCacheId, slotCounter] : mCachedBuffers) {
+        const auto& [hwcCacheSlot, counter] = slotCounter;
+        if (counter < minCounter) {
+            minCounter = counter;
+            minClientCacheId = clientCacheId;
+        }
+    }
+    eraseBufferLocked(minClientCacheId);
+
+    ClientCache::getInstance().unregisterErasedRecipient(minClientCacheId, this);
+}
+
+void HwcSlotGenerator::eraseBufferLocked(const client_cache_t& clientCacheId) REQUIRES(mMutex) {
+    auto itr = mCachedBuffers.find(clientCacheId);
+    if (itr == mCachedBuffers.end()) {
+        return;
+    }
+    auto& [hwcCacheSlot, counter] = itr->second;
+
+    // TODO send to hwc cache and resources
+
+    mFreeHwcCacheSlots.push(hwcCacheSlot);
+    mCachedBuffers.erase(clientCacheId);
+}
+
+} // namespace android
diff --git a/services/surfaceflinger/HwcSlotGenerator.h b/services/surfaceflinger/HwcSlotGenerator.h
new file mode 100644
index 0000000..5a1b6d7
--- /dev/null
+++ b/services/surfaceflinger/HwcSlotGenerator.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2022 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 <functional>
+#include <mutex>
+#include <stack>
+#include <unordered_map>
+
+#include "ClientCache.h"
+
+namespace android {
+
+class HwcSlotGenerator : public ClientCache::ErasedRecipient {
+public:
+    HwcSlotGenerator();
+    void bufferErased(const client_cache_t& clientCacheId);
+    int getHwcCacheSlot(const client_cache_t& clientCacheId);
+
+private:
+    friend class SlotGenerationTest;
+    int addCachedBuffer(const client_cache_t& clientCacheId) REQUIRES(mMutex);
+    int getFreeHwcCacheSlot() REQUIRES(mMutex);
+    void evictLeastRecentlyUsed() REQUIRES(mMutex);
+    void eraseBufferLocked(const client_cache_t& clientCacheId) REQUIRES(mMutex);
+
+    struct CachedBufferHash {
+        std::size_t operator()(const client_cache_t& clientCacheId) const {
+            return std::hash<uint64_t>{}(clientCacheId.id);
+        }
+    };
+
+    std::mutex mMutex;
+
+    std::unordered_map<client_cache_t, std::pair<int /*HwcCacheSlot*/, uint64_t /*counter*/>,
+                       CachedBufferHash>
+            mCachedBuffers GUARDED_BY(mMutex);
+    std::stack<int /*HwcCacheSlot*/> mFreeHwcCacheSlots GUARDED_BY(mMutex);
+
+    // The cache increments this counter value when a slot is updated or used.
+    // Used to track the least recently-used buffer
+    uint64_t mCounter = 0;
+};
+} // namespace android
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index ba3cb51..0091f03 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -78,6 +78,7 @@
 namespace android {
 namespace {
 constexpr int kDumpTableRowLength = 159;
+const ui::Transform kIdentityTransform;
 } // namespace
 
 using namespace ftl::flag_operators;
@@ -2112,8 +2113,6 @@
     layerInfo->set_dataspace(dataspaceDetails(static_cast<android_dataspace>(getDataSpace())));
     layerInfo->set_queued_frames(getQueuedFrameCount());
     layerInfo->set_curr_frame(mCurrentFrameNumber);
-    layerInfo->set_effective_scaling_mode(getEffectiveScalingMode());
-
     layerInfo->set_requested_corner_radius(getDrawingState().cornerRadius);
     layerInfo->set_corner_radius(
             (getRoundedCornerState().radius.x + getRoundedCornerState().radius.y) / 2.0);
@@ -2209,7 +2208,8 @@
     if ((traceFlags & LayerTracing::TRACE_INPUT) && needsInputInfo()) {
         WindowInfo info;
         if (useDrawing) {
-            info = fillInputInfo(ui::Transform(), /* displayIsSecure */ true);
+            info = fillInputInfo(
+                    InputDisplayArgs{.transform = &kIdentityTransform, .isSecure = true});
         } else {
             info = state.inputInfo;
         }
@@ -2416,7 +2416,7 @@
     }
 }
 
-WindowInfo Layer::fillInputInfo(const ui::Transform& displayTransform, bool displayIsSecure) {
+WindowInfo Layer::fillInputInfo(const InputDisplayArgs& displayArgs) {
     if (!hasInputInfo()) {
         mDrawingState.inputInfo.name = getName();
         mDrawingState.inputInfo.ownerUid = mOwnerUid;
@@ -2425,12 +2425,21 @@
         mDrawingState.inputInfo.displayId = getLayerStack().id;
     }
 
+    const ui::Transform& displayTransform =
+            displayArgs.transform != nullptr ? *displayArgs.transform : kIdentityTransform;
+
     WindowInfo info = mDrawingState.inputInfo;
     info.id = sequence;
     info.displayId = getLayerStack().id;
 
     fillInputFrameInfo(info, displayTransform);
 
+    if (displayArgs.transform == nullptr) {
+        // Do not let the window receive touches if it is not associated with a valid display
+        // transform. We still allow the window to receive keys and prevent ANRs.
+        info.inputConfig |= WindowInfo::InputConfig::NOT_TOUCHABLE;
+    }
+
     // For compatibility reasons we let layers which can receive input
     // receive input before they have actually submitted a buffer. Because
     // of this we use canReceiveInput instead of isVisible to check the
@@ -2448,7 +2457,7 @@
 
     // If the window will be blacked out on a display because the display does not have the secure
     // flag and the layer has the secure flag set, then drop input.
-    if (!displayIsSecure && isSecure()) {
+    if (!displayArgs.isSecure && isSecure()) {
         info.inputConfig |= WindowInfo::InputConfig::DROP_INPUT;
     }
 
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index b809c8a..22bb866 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -528,8 +528,6 @@
 
     virtual bool isHdrY410() const { return false; }
 
-    virtual bool shouldPresentNow(nsecs_t /*expectedPresentTime*/) const { return false; }
-
     /*
      * called after composition.
      * returns true if the layer latched a new buffer this frame.
@@ -542,17 +540,13 @@
     // If a buffer was replaced this frame, release the former buffer
     virtual void releasePendingBuffer(nsecs_t /*dequeueReadyTime*/) { }
 
-    virtual void finalizeFrameEventHistory(const std::shared_ptr<FenceTime>& /*glDoneFence*/,
-                                           const CompositorTiming& /*compositorTiming*/) {}
-
     /*
      * latchBuffer - called each time the screen is redrawn and returns whether
      * the visible regions need to be recomputed (this is a fairly heavy
      * operation, so this should be set only if needed). Typically this is used
      * to figure out if the content or size of a surface has changed.
      */
-    virtual bool latchBuffer(bool& /*recomputeVisibleRegions*/, nsecs_t /*latchTime*/,
-                             nsecs_t /*expectedPresentTime*/) {
+    virtual bool latchBuffer(bool& /*recomputeVisibleRegions*/, nsecs_t /*latchTime*/) {
         return false;
     }
 
@@ -854,7 +848,11 @@
     bool getPremultipledAlpha() const;
     void setInputInfo(const gui::WindowInfo& info);
 
-    gui::WindowInfo fillInputInfo(const ui::Transform& displayTransform, bool displayIsSecure);
+    struct InputDisplayArgs {
+        const ui::Transform* transform = nullptr;
+        bool isSecure = false;
+    };
+    gui::WindowInfo fillInputInfo(const InputDisplayArgs& displayArgs);
 
     /**
      * Returns whether this layer has an explicitly set input-info.
@@ -926,11 +924,6 @@
     virtual void commitTransaction(State& stateToCommit);
     virtual void onSurfaceFrameCreated(const std::shared_ptr<frametimeline::SurfaceFrame>&) {}
 
-    // Returns mCurrentScaling mode (originating from the
-    // Client) or mOverrideScalingMode mode (originating from
-    // the Surface Controller) if set.
-    virtual uint32_t getEffectiveScalingMode() const { return 0; }
-
     sp<compositionengine::LayerFE> asLayerFE() const;
     sp<Layer> getClonedFrom() { return mClonedFrom != nullptr ? mClonedFrom.promote() : nullptr; }
     bool isClone() { return mClonedFrom != nullptr; }
diff --git a/services/surfaceflinger/LocklessQueue.h b/services/surfaceflinger/LocklessQueue.h
new file mode 100644
index 0000000..6b63360
--- /dev/null
+++ b/services/surfaceflinger/LocklessQueue.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2022 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 <atomic>
+#include <optional>
+
+template <typename T>
+// Single consumer multi producer stack. We can understand the two operations independently to see
+// why they are without race condition.
+//
+// push is responsible for maintaining a linked list stored in mPush, and called from multiple
+// threads without lock. We can see that if two threads never observe the same value from
+// mPush.load, it just functions as a normal linked list. In the case where two threads observe the
+// same value, one of them has to execute the compare_exchange first. The one that doesn't execute
+// the compare exchange first, will receive false from compare_exchange. previousHead is updated (by
+// compare_exchange) to the most recent value of mPush, and we try again. It's relatively clear to
+// see that the process can repeat with an arbitrary number of threads.
+//
+// Pop is much simpler. If mPop is empty (as it begins) it atomically exchanges
+// the entire push list with null. This is safe, since the only other reader (push)
+// of mPush will retry if it changes in between it's read and atomic compare. We
+// then store the list and pop one element.
+//
+// If we already had something in the pop list we just pop directly.
+class LocklessQueue {
+public:
+    class Entry {
+    public:
+        T mValue;
+        std::atomic<Entry*> mNext;
+        Entry(T value) : mValue(value) {}
+    };
+    std::atomic<Entry*> mPush = nullptr;
+    std::atomic<Entry*> mPop = nullptr;
+    bool isEmpty() { return (mPush.load() == nullptr) && (mPop.load() == nullptr); }
+
+    void push(T value) {
+        Entry* entry = new Entry(value);
+        Entry* previousHead = mPush.load(/*std::memory_order_relaxed*/);
+        do {
+            entry->mNext = previousHead;
+        } while (!mPush.compare_exchange_weak(previousHead, entry)); /*std::memory_order_release*/
+    }
+    std::optional<T> pop() {
+        Entry* popped = mPop.load(/*std::memory_order_acquire*/);
+        if (popped) {
+            // Single consumer so this is fine
+            mPop.store(popped->mNext /* , std::memory_order_release */);
+            auto value = popped->mValue;
+            delete popped;
+            return std::move(value);
+        } else {
+            Entry* grabbedList = mPush.exchange(nullptr /* , std::memory_order_acquire */);
+            if (!grabbedList) return std::nullopt;
+            // Reverse the list
+            while (grabbedList->mNext) {
+                Entry* next = grabbedList->mNext;
+                grabbedList->mNext = popped;
+                popped = grabbedList;
+                grabbedList = next;
+            }
+            mPop.store(popped /* , std::memory_order_release */);
+            auto value = grabbedList->mValue;
+            delete grabbedList;
+            return std::move(value);
+        }
+    }
+};
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 3b15ac4..215f083 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -3264,11 +3264,11 @@
     mDrawingState.traverseInReverseZOrder([&](Layer* layer) {
         if (!layer->needsInputInfo()) return;
 
-        // Do not create WindowInfos for windows on displays that cannot receive input.
-        if (const auto opt = displayInputInfos.get(layer->getLayerStack())) {
-            const auto& info = opt->get();
-            outWindowInfos.push_back(layer->fillInputInfo(info.transform, info.isSecure));
-        }
+        const auto opt = displayInputInfos.get(layer->getLayerStack(),
+                                               [](const auto& info) -> Layer::InputDisplayArgs {
+                                                   return {&info.transform, info.isSecure};
+                                               });
+        outWindowInfos.push_back(layer->fillInputInfo(opt.value_or(Layer::InputDisplayArgs{})));
     });
 
     sNumWindowInfos = outWindowInfos.size();
@@ -3488,8 +3488,6 @@
     bool frameQueued = false;
     bool newDataLatched = false;
 
-    const nsecs_t expectedPresentTime = mExpectedPresentTime.load();
-
     // Store the set of layers that need updates. This set must not change as
     // buffers are being latched, as this could result in a deadlock.
     // Example: Two producers share the same command stream and:
@@ -3509,12 +3507,7 @@
 
         if (layer->hasReadyFrame()) {
             frameQueued = true;
-            if (layer->shouldPresentNow(expectedPresentTime)) {
-                mLayersWithQueuedFrames.emplace(layer);
-            } else {
-                ATRACE_NAME("!layer->shouldPresentNow()");
-                layer->useEmptyDamage();
-            }
+            mLayersWithQueuedFrames.emplace(layer);
         } else {
             layer->useEmptyDamage();
         }
@@ -3535,7 +3528,7 @@
         Mutex::Autolock lock(mStateLock);
 
         for (const auto& layer : mLayersWithQueuedFrames) {
-            if (layer->latchBuffer(visibleRegions, latchTime, expectedPresentTime)) {
+            if (layer->latchBuffer(visibleRegions, latchTime)) {
                 mLayersPendingRefresh.push_back(layer);
                 newDataLatched = true;
             }
@@ -3701,6 +3694,8 @@
 
             transactions.emplace_back(std::move(transaction));
             transactionQueue.pop();
+            mPendingTransactionCount--;
+            ATRACE_INT("TransactionQueue", mPendingTransactionCount.load());
         }
 
         if (transactionQueue.empty()) {
@@ -3724,8 +3719,6 @@
     {
         Mutex::Autolock _l(mStateLock);
         {
-            Mutex::Autolock _l(mQueueLock);
-
             int lastTransactionsPendingBarrier = 0;
             int transactionsPendingBarrier = 0;
             // First collect transactions from the pending transaction queues.
@@ -3738,8 +3731,12 @@
             // Second, collect transactions from the transaction queue.
             // Here as well we are not allowing unsignaled buffers for the same
             // reason as above.
-            while (!mTransactionQueue.empty()) {
-                auto& transaction = mTransactionQueue.front();
+            while (!mLocklessTransactionQueue.isEmpty()) {
+                auto maybeTransaction = mLocklessTransactionQueue.pop();
+                if (!maybeTransaction.has_value()) {
+                    break;
+                }
+                auto transaction = maybeTransaction.value();
                 const bool pendingTransactions =
                         mPendingTransactionQueues.find(transaction.applyToken) !=
                         mPendingTransactionQueues.end();
@@ -3777,9 +3774,9 @@
                         }
                     });
                     transactions.emplace_back(std::move(transaction));
+                    mPendingTransactionCount--;
+                    ATRACE_INT("TransactionQueue", mPendingTransactionCount.load());
                 }
-                mTransactionQueue.pop_front();
-                ATRACE_INT("TransactionQueue", mTransactionQueue.size());
             }
 
             // Transactions with a buffer pending on a barrier may be on a different applyToken
@@ -3840,8 +3837,7 @@
 }
 
 bool SurfaceFlinger::transactionFlushNeeded() {
-    Mutex::Autolock _l(mQueueLock);
-    return !mPendingTransactionQueues.empty() || !mTransactionQueue.empty();
+    return !mPendingTransactionQueues.empty() || !mLocklessTransactionQueue.isEmpty();
 }
 
 bool SurfaceFlinger::frameIsEarly(nsecs_t expectedPresentTime, int64_t vsyncId) const {
@@ -4008,17 +4004,15 @@
 
 void SurfaceFlinger::queueTransaction(TransactionState& state) {
     state.queueTime = systemTime();
-
-    Mutex::Autolock lock(mQueueLock);
-
     // Generate a CountDownLatch pending state if this is a synchronous transaction.
     if (state.flags & eSynchronous) {
         state.transactionCommittedSignal =
                 std::make_shared<CountDownLatch>(CountDownLatch::eSyncTransaction);
     }
 
-    mTransactionQueue.emplace_back(state);
-    ATRACE_INT("TransactionQueue", mTransactionQueue.size());
+    mLocklessTransactionQueue.push(state);
+    mPendingTransactionCount++;
+    ATRACE_INT("TransactionQueue", mPendingTransactionCount.load());
 
     const auto schedule = [](uint32_t flags) {
         if (flags & eEarlyWakeupEnd) return TransactionSchedule::EarlyEnd;
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index a247bae..46d941f 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -66,6 +66,7 @@
 #include "FlagManager.h"
 #include "FrameTracker.h"
 #include "LayerVector.h"
+#include "LocklessQueue.h"
 #include "Scheduler/RefreshRateConfigs.h"
 #include "Scheduler/RefreshRateStats.h"
 #include "Scheduler/Scheduler.h"
@@ -754,13 +755,13 @@
             std::vector<TransactionState>& transactions,
             std::unordered_map<sp<IBinder>, uint64_t, SpHash<IBinder>>& bufferLayersReadyToPresent,
             std::unordered_set<sp<IBinder>, SpHash<IBinder>>& applyTokensWithUnsignaledTransactions,
-            bool tryApplyUnsignaled) REQUIRES(mStateLock, mQueueLock);
+            bool tryApplyUnsignaled) REQUIRES(mStateLock);
 
     int flushUnsignaledPendingTransactionQueues(
             std::vector<TransactionState>& transactions,
             std::unordered_map<sp<IBinder>, uint64_t, SpHash<IBinder>>& bufferLayersReadyToPresent,
             std::unordered_set<sp<IBinder>, SpHash<IBinder>>& applyTokensWithUnsignaledTransactions)
-            REQUIRES(mStateLock, mQueueLock);
+            REQUIRES(mStateLock);
 
     uint32_t setClientStateLocked(const FrameTimelineInfo&, ComposerState&,
                                   int64_t desiredPresentTime, bool isAutoTimestamp,
@@ -1086,7 +1087,7 @@
     status_t CheckTransactCodeCredentials(uint32_t code);
 
     // Add transaction to the Transaction Queue
-    void queueTransaction(TransactionState& state) EXCLUDES(mQueueLock);
+    void queueTransaction(TransactionState& state);
     void waitForSynchronousTransaction(const CountDownLatch& transactionCommittedSignal);
     void signalSynchronousTransactions(const uint32_t flag);
 
@@ -1244,11 +1245,11 @@
     uint32_t mTexturePoolSize = 0;
     std::vector<uint32_t> mTexturePool;
 
-    mutable Mutex mQueueLock;
     Condition mTransactionQueueCV;
     std::unordered_map<sp<IBinder>, std::queue<TransactionState>, IListenerHash>
-            mPendingTransactionQueues GUARDED_BY(mQueueLock);
-    std::deque<TransactionState> mTransactionQueue GUARDED_BY(mQueueLock);
+            mPendingTransactionQueues;
+    LocklessQueue<TransactionState> mLocklessTransactionQueue;
+    std::atomic<size_t> mPendingTransactionCount = 0;
     /*
      * Feature prototyping
      */
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
index b41d7b9..ed57e18 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
@@ -724,7 +724,7 @@
         return mFlinger->setPowerModeInternal(display, mode);
     }
 
-    auto &getTransactionQueue() { return mFlinger->mTransactionQueue; }
+    auto &getTransactionQueue() { return mFlinger->mLocklessTransactionQueue; }
     auto &getPendingTransactionQueue() { return mFlinger->mPendingTransactionQueues; }
 
     auto setTransactionState(
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp
index 4d90b1e..ed94e76 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp
@@ -125,9 +125,7 @@
             ftl::yield<FenceResult>(base::unexpected(mFdp.ConsumeIntegral<status_t>())).share());
 
     layer->releasePendingBuffer(mFdp.ConsumeIntegral<int64_t>());
-    layer->finalizeFrameEventHistory(fenceTime, compositorTiming);
     layer->onPostComposition(nullptr, fenceTime, fenceTime, compositorTiming);
-    layer->isBufferDue(mFdp.ConsumeIntegral<int64_t>());
 
     layer->setTransform(mFdp.ConsumeIntegral<uint32_t>());
     layer->setTransformToDisplayInverse(mFdp.ConsumeBool());
@@ -151,7 +149,6 @@
     layer->computeSourceBounds(getFuzzedFloatRect(&mFdp));
 
     layer->fenceHasSignaled();
-    layer->framePresentTimeIsCurrent(mFdp.ConsumeIntegral<int64_t>());
     layer->onPreComposition(mFdp.ConsumeIntegral<int64_t>());
     const std::vector<sp<CallbackHandle>> callbacks;
     layer->setTransactionCompletedListeners(callbacks);
diff --git a/services/surfaceflinger/tests/unittests/CachingTest.cpp b/services/surfaceflinger/tests/unittests/CachingTest.cpp
index 6f85498..7f203ce 100644
--- a/services/surfaceflinger/tests/unittests/CachingTest.cpp
+++ b/services/surfaceflinger/tests/unittests/CachingTest.cpp
@@ -26,8 +26,7 @@
 
 class SlotGenerationTest : public testing::Test {
 protected:
-    sp<BufferStateLayer::HwcSlotGenerator> mHwcSlotGenerator =
-            sp<BufferStateLayer::HwcSlotGenerator>::make();
+    sp<HwcSlotGenerator> mHwcSlotGenerator = sp<HwcSlotGenerator>::make();
     sp<GraphicBuffer> mBuffer1{new GraphicBuffer(1, 1, HAL_PIXEL_FORMAT_RGBA_8888, 1, 0)};
     sp<GraphicBuffer> mBuffer2{new GraphicBuffer(1, 1, HAL_PIXEL_FORMAT_RGBA_8888, 1, 0)};
     sp<GraphicBuffer> mBuffer3{new GraphicBuffer(10, 10, HAL_PIXEL_FORMAT_RGBA_8888, 1, 0)};
diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
index f6ebfe5..9c16e4c 100644
--- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
@@ -511,7 +511,7 @@
                 Region(Rect(LayerProperties::HEIGHT, LayerProperties::WIDTH)));
 
         bool ignoredRecomputeVisibleRegions;
-        layer->latchBuffer(ignoredRecomputeVisibleRegions, 0, 0);
+        layer->latchBuffer(ignoredRecomputeVisibleRegions, 0);
         Mock::VerifyAndClear(test->mRenderEngine);
     }
 
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 81e3f4a..77cefa6 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -421,7 +421,7 @@
         return mFlinger->SurfaceFlinger::getDisplayNativePrimaries(displayToken, primaries);
     }
 
-    auto& getTransactionQueue() { return mFlinger->mTransactionQueue; }
+    auto& getTransactionQueue() { return mFlinger->mLocklessTransactionQueue; }
     auto& getPendingTransactionQueue() { return mFlinger->mPendingTransactionQueues; }
     auto& getTransactionCommittedSignals() { return mFlinger->mTransactionCommittedSignals; }
 
diff --git a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
index 84f1170..b2da34e 100644
--- a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
@@ -117,7 +117,7 @@
     }
 
     void NotPlacedOnTransactionQueue(uint32_t flags) {
-        ASSERT_EQ(0u, mFlinger.getTransactionQueue().size());
+        ASSERT_TRUE(mFlinger.getTransactionQueue().isEmpty());
         EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
         TransactionInfo transaction;
         setupSingle(transaction, flags,
@@ -140,12 +140,12 @@
             EXPECT_LE(returnedTime, applicationTime + mFlinger.getAnimationTransactionTimeout());
         }
         // Each transaction should have been placed on the transaction queue
-        auto transactionQueue = mFlinger.getTransactionQueue();
-        EXPECT_EQ(1u, transactionQueue.size());
+        auto& transactionQueue = mFlinger.getTransactionQueue();
+        EXPECT_FALSE(transactionQueue.isEmpty());
     }
 
     void PlaceOnTransactionQueue(uint32_t flags) {
-        ASSERT_EQ(0u, mFlinger.getTransactionQueue().size());
+        ASSERT_TRUE(mFlinger.getTransactionQueue().isEmpty());
         EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
 
         // first check will see desired present time has not passed,
@@ -171,12 +171,12 @@
                       applicationSentTime + mFlinger.getAnimationTransactionTimeout());
         }
         // This transaction should have been placed on the transaction queue
-        auto transactionQueue = mFlinger.getTransactionQueue();
-        EXPECT_EQ(1u, transactionQueue.size());
+        auto& transactionQueue = mFlinger.getTransactionQueue();
+        EXPECT_FALSE(transactionQueue.isEmpty());
     }
 
     void BlockedByPriorTransaction(uint32_t flags) {
-        ASSERT_EQ(0u, mFlinger.getTransactionQueue().size());
+        ASSERT_TRUE(mFlinger.getTransactionQueue().isEmpty());
         nsecs_t time = systemTime();
         EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(2);
 
@@ -239,8 +239,8 @@
     int mTransactionNumber = 0;
 };
 
-TEST_F(TransactionApplicationTest, Flush_RemovesFromQueue) {
-    ASSERT_EQ(0u, mFlinger.getTransactionQueue().size());
+TEST_F(TransactionApplicationTest, AddToPendingQueue) {
+    ASSERT_TRUE(mFlinger.getTransactionQueue().isEmpty());
     EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
 
     TransactionInfo transactionA; // transaction to go on pending queue
@@ -253,10 +253,27 @@
                                  mHasListenerCallbacks, mCallbacks, transactionA.id);
 
     auto& transactionQueue = mFlinger.getTransactionQueue();
-    ASSERT_EQ(1u, transactionQueue.size());
+    ASSERT_FALSE(transactionQueue.isEmpty());
 
-    auto& transactionState = transactionQueue.front();
+    auto transactionState = transactionQueue.pop().value();
     checkEqual(transactionA, transactionState);
+}
+
+TEST_F(TransactionApplicationTest, Flush_RemovesFromQueue) {
+    ASSERT_TRUE(mFlinger.getTransactionQueue().isEmpty());
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+
+    TransactionInfo transactionA; // transaction to go on pending queue
+    setupSingle(transactionA, /*flags*/ 0, /*desiredPresentTime*/ s2ns(1), false,
+                FrameTimelineInfo{});
+    mFlinger.setTransactionState(transactionA.frameTimelineInfo, transactionA.states,
+                                 transactionA.displays, transactionA.flags, transactionA.applyToken,
+                                 transactionA.inputWindowCommands, transactionA.desiredPresentTime,
+                                 transactionA.isAutoTimestamp, transactionA.uncacheBuffer,
+                                 mHasListenerCallbacks, mCallbacks, transactionA.id);
+
+    auto& transactionQueue = mFlinger.getTransactionQueue();
+    ASSERT_FALSE(transactionQueue.isEmpty());
 
     // because flushing uses the cached expected present time, we send an empty
     // transaction here (sending a null applyToken to fake it as from a
@@ -272,7 +289,7 @@
     // passed
     mFlinger.flushTransactionQueues();
 
-    EXPECT_EQ(0u, transactionQueue.size());
+    EXPECT_TRUE(mFlinger.getTransactionQueue().isEmpty());
 }
 
 TEST_F(TransactionApplicationTest, NotPlacedOnTransactionQueue_Synchronous) {
@@ -306,7 +323,9 @@
     void TearDown() override {
         // Clear all transaction queues to release all transactions we sent
         // in the tests. Otherwise, gmock complains about memory leaks.
-        mFlinger.getTransactionQueue().clear();
+        while (!mFlinger.getTransactionQueue().isEmpty()) {
+            mFlinger.getTransactionQueue().pop();
+        }
         mFlinger.getPendingTransactionQueue().clear();
         mFlinger.getTransactionCommittedSignals().clear();
         mFlinger.commitTransactionsLocked(eTransactionMask);
@@ -358,7 +377,7 @@
     void setTransactionStates(const std::vector<TransactionInfo>& transactions,
                               size_t expectedTransactionsApplied,
                               size_t expectedTransactionsPending) {
-        EXPECT_EQ(0u, mFlinger.getTransactionQueue().size());
+        EXPECT_TRUE(mFlinger.getTransactionQueue().isEmpty());
         EXPECT_EQ(0u, mFlinger.getPendingTransactionQueue().size());
 
         for (const auto& transaction : transactions) {
@@ -370,7 +389,7 @@
                                          mHasListenerCallbacks, mCallbacks, transaction.id);
         }
         mFlinger.flushTransactionQueues();
-        EXPECT_EQ(0u, mFlinger.getTransactionQueue().size());
+        EXPECT_TRUE(mFlinger.getTransactionQueue().isEmpty());
         EXPECT_EQ(expectedTransactionsPending, mFlinger.getPendingTransactionQueue().size());
         EXPECT_EQ(expectedTransactionsApplied, mFlinger.getTransactionCommittedSignals().size());
     }
diff --git a/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp b/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp
index 5364630..28bf8bf 100644
--- a/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp
@@ -127,7 +127,6 @@
                          dequeueTime, FrameTimelineInfo{});
 
         commitTransaction(layer.get());
-        bool computeVisisbleRegions;
         nsecs_t latchTime = 25;
         EXPECT_CALL(*mFlinger.getFrameTracer(),
                     traceFence(layerId, bufferId, frameNumber, _,
@@ -135,7 +134,7 @@
         EXPECT_CALL(*mFlinger.getFrameTracer(),
                     traceTimestamp(layerId, bufferId, frameNumber, latchTime,
                                    FrameTracer::FrameEvent::LATCH, /*duration*/ 0));
-        layer->updateTexImage(computeVisisbleRegions, latchTime, /*expectedPresentTime*/ 0);
+        layer->updateTexImage(latchTime);
 
         auto glDoneFence = fenceFactory.createFenceTimeForTest(fence);
         auto presentFence = fenceFactory.createFenceTimeForTest(fence);
diff --git a/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp b/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp
index 6d58303..e943644 100644
--- a/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp
@@ -140,8 +140,7 @@
         // Buffers are presented only at latch time.
         EXPECT_EQ(PresentState::Unknown, surfaceFrame->getPresentState());
 
-        bool computeVisisbleRegions;
-        layer->updateTexImage(computeVisisbleRegions, 15, 0);
+        layer->updateTexImage(15);
 
         EXPECT_EQ(1, surfaceFrame->getToken());
         EXPECT_EQ(true, surfaceFrame->getIsBuffer());
@@ -192,8 +191,7 @@
         const auto presentedSurfaceFrame = layer->mDrawingState.bufferSurfaceFrameTX;
 
         commitTransaction(layer.get());
-        bool computeVisisbleRegions;
-        layer->updateTexImage(computeVisisbleRegions, 15, 0);
+        layer->updateTexImage(15);
 
         EXPECT_EQ(1, droppedSurfaceFrame->getToken());
         EXPECT_EQ(true, droppedSurfaceFrame->getIsBuffer());
@@ -243,8 +241,7 @@
         // Buffers are presented only at latch time.
         EXPECT_EQ(PresentState::Unknown, surfaceFrame->getPresentState());
 
-        bool computeVisisbleRegions;
-        layer->updateTexImage(computeVisisbleRegions, 15, 0);
+        layer->updateTexImage(15);
 
         EXPECT_EQ(PresentState::Presented, surfaceFrame->getPresentState());
     }
@@ -331,8 +328,7 @@
         // Buffers are presented only at latch time.
         EXPECT_EQ(PresentState::Unknown, bufferSurfaceFrameTX->getPresentState());
 
-        bool computeVisisbleRegions;
-        layer->updateTexImage(computeVisisbleRegions, 15, 0);
+        layer->updateTexImage(15);
 
         EXPECT_EQ(PresentState::Presented, bufferSurfaceFrameTX->getPresentState());
     }
@@ -377,8 +373,7 @@
         auto presentedSurfaceFrame = layer->mDrawingState.bufferSurfaceFrameTX;
 
         commitTransaction(layer.get());
-        bool computeVisisbleRegions;
-        layer->updateTexImage(computeVisisbleRegions, 15, 0);
+        layer->updateTexImage(15);
 
         // Both the droppedSurfaceFrame and presentedSurfaceFrame should be in
         // pendingJankClassifications.
@@ -458,8 +453,7 @@
         const auto presentedSurfaceFrame = layer->mDrawingState.bufferSurfaceFrameTX;
 
         commitTransaction(layer.get());
-        bool computeVisisbleRegions;
-        layer->updateTexImage(computeVisisbleRegions, 15, 0);
+        layer->updateTexImage(15);
 
         EXPECT_EQ(1, droppedSurfaceFrame1->getToken());
         EXPECT_EQ(true, droppedSurfaceFrame1->getIsBuffer());
@@ -517,8 +511,7 @@
         }
 
         auto presentedBufferSurfaceFrame = layer->mDrawingState.bufferSurfaceFrameTX;
-        bool computeVisisbleRegions;
-        layer->updateTexImage(computeVisisbleRegions, 15, 0);
+        layer->updateTexImage(15);
         // BufferlessSurfaceFrames are immediately set to presented and added to the DisplayFrame.
         // Since we don't have access to DisplayFrame here, trigger an onPresent directly.
         for (auto& surfaceFrame : bufferlessSurfaceFrames) {