Merge "SF: Handle buffer scale transforms when calculating layer geometry"
diff --git a/include/input/InputWindow.h b/include/input/InputWindow.h
index a065a4c..916af69 100644
--- a/include/input/InputWindow.h
+++ b/include/input/InputWindow.h
@@ -116,7 +116,7 @@
         INPUT_FEATURE_NO_INPUT_CHANNEL = 0x00000002,
         INPUT_FEATURE_DISABLE_USER_ACTIVITY = 0x00000004,
     };
-    
+
     /* These values are filled in by the WM and passed through SurfaceFlinger
      * unless specified otherwise.
      */
@@ -165,6 +165,8 @@
     int32_t displayId;
     int32_t portalToDisplayId = ADISPLAY_ID_NONE;
     InputApplicationInfo applicationInfo;
+    bool replaceTouchableRegionWithCrop;
+    wp<IBinder> touchableRegionCropHandle;
 
     void addTouchableRegion(const Rect& region);
 
diff --git a/libs/input/InputWindow.cpp b/libs/input/InputWindow.cpp
index 5c5613d..5a60347 100644
--- a/libs/input/InputWindow.cpp
+++ b/libs/input/InputWindow.cpp
@@ -98,7 +98,8 @@
     output.writeInt32(portalToDisplayId);
     applicationInfo.write(output);
     output.write(touchableRegion);
-
+    output.writeBool(replaceTouchableRegionWithCrop);
+    output.writeWeakBinder(touchableRegionCropHandle);
     return OK;
 }
 
@@ -140,6 +141,8 @@
     ret.portalToDisplayId = from.readInt32();
     ret.applicationInfo = InputApplicationInfo::read(from);
     from.read(ret.touchableRegion);
+    ret.replaceTouchableRegionWithCrop = from.readBool();
+    ret.touchableRegionCropHandle = from.readWeakBinder();
 
     return ret;
 }
diff --git a/libs/input/tests/InputWindow_test.cpp b/libs/input/tests/InputWindow_test.cpp
index 09dd72b..6db18ab 100644
--- a/libs/input/tests/InputWindow_test.cpp
+++ b/libs/input/tests/InputWindow_test.cpp
@@ -37,6 +37,7 @@
 }
 
 TEST(InputWindowInfo, Parcelling) {
+    sp<IBinder> touchableRegionCropHandle = new BBinder();
     InputWindowInfo i;
     i.token = new BBinder();
     i.name = "Foobar";
@@ -62,6 +63,8 @@
     i.inputFeatures = 29;
     i.displayId = 34;
     i.portalToDisplayId = 2;
+    i.replaceTouchableRegionWithCrop = true;
+    i.touchableRegionCropHandle = touchableRegionCropHandle;
 
     Parcel p;
     i.write(p);
@@ -92,6 +95,8 @@
     ASSERT_EQ(i.inputFeatures, i2.inputFeatures);
     ASSERT_EQ(i.displayId, i2.displayId);
     ASSERT_EQ(i.portalToDisplayId, i2.portalToDisplayId);
+    ASSERT_EQ(i.replaceTouchableRegionWithCrop, i2.replaceTouchableRegionWithCrop);
+    ASSERT_EQ(i.touchableRegionCropHandle, i2.touchableRegionCropHandle);
 }
 
 } // namespace test
diff --git a/libs/renderengine/gl/GLESRenderEngine.cpp b/libs/renderengine/gl/GLESRenderEngine.cpp
index 8069a1a..f651309 100644
--- a/libs/renderengine/gl/GLESRenderEngine.cpp
+++ b/libs/renderengine/gl/GLESRenderEngine.cpp
@@ -432,10 +432,15 @@
 }
 
 GLESRenderEngine::~GLESRenderEngine() {
-    for (const auto& image : mFramebufferImageCache) {
-        eglDestroyImageKHR(mEGLDisplay, image.second);
+    std::lock_guard<std::mutex> lock(mRenderingMutex);
+    unbindFrameBuffer(mDrawingBuffer.get());
+    mDrawingBuffer = nullptr;
+    while (!mFramebufferImageCache.empty()) {
+        EGLImageKHR expired = mFramebufferImageCache.front().second;
+        mFramebufferImageCache.pop_front();
+        eglDestroyImageKHR(mEGLDisplay, expired);
     }
-    mFramebufferImageCache.clear();
+    mImageCache.clear();
     eglMakeCurrent(mEGLDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
     eglTerminate(mEGLDisplay);
 }
@@ -627,6 +632,16 @@
 
 status_t GLESRenderEngine::bindExternalTextureBuffer(uint32_t texName, sp<GraphicBuffer> buffer,
                                                      sp<Fence> bufferFence) {
+    std::lock_guard<std::mutex> lock(mRenderingMutex);
+    return bindExternalTextureBufferLocked(texName, buffer, bufferFence);
+}
+
+status_t GLESRenderEngine::bindExternalTextureBufferLocked(uint32_t texName,
+                                                           sp<GraphicBuffer> buffer,
+                                                           sp<Fence> bufferFence) {
+    if (buffer == nullptr) {
+        return BAD_VALUE;
+    }
     ATRACE_CALL();
     auto cachedImage = mImageCache.find(buffer->getId());
 
@@ -675,6 +690,7 @@
 }
 
 void GLESRenderEngine::unbindExternalTextureBuffer(uint64_t bufferId) {
+    std::lock_guard<std::mutex> lock(mRenderingMutex);
     const auto& cachedImage = mImageCache.find(bufferId);
     if (cachedImage != mImageCache.end()) {
         ALOGV("Destroying image for buffer: %" PRIu64, bufferId);
@@ -771,7 +787,7 @@
 EGLImageKHR GLESRenderEngine::createFramebufferImageIfNeeded(ANativeWindowBuffer* nativeBuffer,
                                                              bool isProtected) {
     sp<GraphicBuffer> graphicBuffer = GraphicBuffer::from(nativeBuffer);
-    uint32_t bufferId = graphicBuffer->getId();
+    uint64_t bufferId = graphicBuffer->getId();
     for (const auto& image : mFramebufferImageCache) {
         if (image.first == bufferId) {
             return image.second;
@@ -810,114 +826,125 @@
         sync_wait(bufferFence.get(), -1);
     }
 
-    BindNativeBufferAsFramebuffer fbo(*this, buffer);
-
-    if (fbo.getStatus() != NO_ERROR) {
-        ALOGE("Failed to bind framebuffer! Aborting GPU composition for buffer (%p).",
-              buffer->handle);
-        checkErrors();
-        return fbo.getStatus();
+    if (buffer == nullptr) {
+        ALOGE("No output buffer provided. Aborting GPU composition.");
+        return BAD_VALUE;
     }
 
-    // clear the entire buffer, sometimes when we reuse buffers we'd persist
-    // ghost images otherwise.
-    // we also require a full transparent framebuffer for overlays. This is
-    // probably not quite efficient on all GPUs, since we could filter out
-    // opaque layers.
-    clearWithColor(0.0, 0.0, 0.0, 0.0);
+    {
+        std::lock_guard<std::mutex> lock(mRenderingMutex);
 
-    setViewportAndProjection(display.physicalDisplay, display.clip);
+        BindNativeBufferAsFramebuffer fbo(*this, buffer);
 
-    setOutputDataSpace(display.outputDataspace);
-    setDisplayMaxLuminance(display.maxLuminance);
-
-    mat4 projectionMatrix = mState.projectionMatrix * display.globalTransform;
-    mState.projectionMatrix = projectionMatrix;
-    if (!display.clearRegion.isEmpty()) {
-        glDisable(GL_BLEND);
-        fillRegionWithColor(display.clearRegion, 0.0, 0.0, 0.0, 1.0);
-    }
-
-    Mesh mesh(Mesh::TRIANGLE_FAN, 4, 2, 2);
-    for (auto layer : layers) {
-        mState.projectionMatrix = projectionMatrix * layer.geometry.positionTransform;
-
-        const FloatRect bounds = layer.geometry.boundaries;
-        Mesh::VertexArray<vec2> position(mesh.getPositionArray<vec2>());
-        position[0] = vec2(bounds.left, bounds.top);
-        position[1] = vec2(bounds.left, bounds.bottom);
-        position[2] = vec2(bounds.right, bounds.bottom);
-        position[3] = vec2(bounds.right, bounds.top);
-
-        setupLayerCropping(layer, mesh);
-        setColorTransform(display.colorTransform * layer.colorTransform);
-
-        bool usePremultipliedAlpha = true;
-        bool disableTexture = true;
-        bool isOpaque = false;
-
-        if (layer.source.buffer.buffer != nullptr) {
-            disableTexture = false;
-            isOpaque = layer.source.buffer.isOpaque;
-
-            sp<GraphicBuffer> gBuf = layer.source.buffer.buffer;
-            bindExternalTextureBuffer(layer.source.buffer.textureName, gBuf,
-                                      layer.source.buffer.fence);
-
-            usePremultipliedAlpha = layer.source.buffer.usePremultipliedAlpha;
-            Texture texture(Texture::TEXTURE_EXTERNAL, layer.source.buffer.textureName);
-            mat4 texMatrix = layer.source.buffer.textureTransform;
-
-            texture.setMatrix(texMatrix.asArray());
-            texture.setFiltering(layer.source.buffer.useTextureFiltering);
-
-            texture.setDimensions(gBuf->getWidth(), gBuf->getHeight());
-            setSourceY410BT2020(layer.source.buffer.isY410BT2020);
-
-            renderengine::Mesh::VertexArray<vec2> texCoords(mesh.getTexCoordArray<vec2>());
-            texCoords[0] = vec2(0.0, 0.0);
-            texCoords[1] = vec2(0.0, 1.0);
-            texCoords[2] = vec2(1.0, 1.0);
-            texCoords[3] = vec2(1.0, 0.0);
-            setupLayerTexturing(texture);
-        }
-
-        const half3 solidColor = layer.source.solidColor;
-        const half4 color = half4(solidColor.r, solidColor.g, solidColor.b, layer.alpha);
-        // Buffer sources will have a black solid color ignored in the shader,
-        // so in that scenario the solid color passed here is arbitrary.
-        setupLayerBlending(usePremultipliedAlpha, isOpaque, disableTexture, color,
-                           layer.geometry.roundedCornersRadius);
-        if (layer.disableBlending) {
-            glDisable(GL_BLEND);
-        }
-        setSourceDataSpace(layer.sourceDataspace);
-
-        drawMesh(mesh);
-
-        // Cleanup if there's a buffer source
-        if (layer.source.buffer.buffer != nullptr) {
-            disableBlending();
-            setSourceY410BT2020(false);
-            disableTexturing();
-        }
-    }
-
-    *drawFence = flush();
-    // If flush failed or we don't support native fences, we need to force the
-    // gl command stream to be executed.
-    if (drawFence->get() < 0) {
-        bool success = finish();
-        if (!success) {
-            ALOGE("Failed to flush RenderEngine commands");
+        if (fbo.getStatus() != NO_ERROR) {
+            ALOGE("Failed to bind framebuffer! Aborting GPU composition for buffer (%p).",
+                  buffer->handle);
             checkErrors();
-            // Chances are, something illegal happened (either the caller passed
-            // us bad parameters, or we messed up our shader generation).
-            return INVALID_OPERATION;
+            return fbo.getStatus();
         }
-    }
 
-    checkErrors();
+        // clear the entire buffer, sometimes when we reuse buffers we'd persist
+        // ghost images otherwise.
+        // we also require a full transparent framebuffer for overlays. This is
+        // probably not quite efficient on all GPUs, since we could filter out
+        // opaque layers.
+        clearWithColor(0.0, 0.0, 0.0, 0.0);
+
+        setViewportAndProjection(display.physicalDisplay, display.clip);
+
+        setOutputDataSpace(display.outputDataspace);
+        setDisplayMaxLuminance(display.maxLuminance);
+
+        mat4 projectionMatrix = mState.projectionMatrix * display.globalTransform;
+        mState.projectionMatrix = projectionMatrix;
+        if (!display.clearRegion.isEmpty()) {
+            glDisable(GL_BLEND);
+            fillRegionWithColor(display.clearRegion, 0.0, 0.0, 0.0, 1.0);
+        }
+
+        Mesh mesh(Mesh::TRIANGLE_FAN, 4, 2, 2);
+        for (auto layer : layers) {
+            mState.projectionMatrix = projectionMatrix * layer.geometry.positionTransform;
+
+            const FloatRect bounds = layer.geometry.boundaries;
+            Mesh::VertexArray<vec2> position(mesh.getPositionArray<vec2>());
+            position[0] = vec2(bounds.left, bounds.top);
+            position[1] = vec2(bounds.left, bounds.bottom);
+            position[2] = vec2(bounds.right, bounds.bottom);
+            position[3] = vec2(bounds.right, bounds.top);
+
+            setupLayerCropping(layer, mesh);
+            setColorTransform(display.colorTransform * layer.colorTransform);
+
+            bool usePremultipliedAlpha = true;
+            bool disableTexture = true;
+            bool isOpaque = false;
+
+            if (layer.source.buffer.buffer != nullptr) {
+                disableTexture = false;
+                isOpaque = layer.source.buffer.isOpaque;
+
+                sp<GraphicBuffer> gBuf = layer.source.buffer.buffer;
+                bindExternalTextureBufferLocked(layer.source.buffer.textureName, gBuf,
+                                                layer.source.buffer.fence);
+
+                usePremultipliedAlpha = layer.source.buffer.usePremultipliedAlpha;
+                Texture texture(Texture::TEXTURE_EXTERNAL, layer.source.buffer.textureName);
+                mat4 texMatrix = layer.source.buffer.textureTransform;
+
+                texture.setMatrix(texMatrix.asArray());
+                texture.setFiltering(layer.source.buffer.useTextureFiltering);
+
+                texture.setDimensions(gBuf->getWidth(), gBuf->getHeight());
+                setSourceY410BT2020(layer.source.buffer.isY410BT2020);
+
+                renderengine::Mesh::VertexArray<vec2> texCoords(mesh.getTexCoordArray<vec2>());
+                texCoords[0] = vec2(0.0, 0.0);
+                texCoords[1] = vec2(0.0, 1.0);
+                texCoords[2] = vec2(1.0, 1.0);
+                texCoords[3] = vec2(1.0, 0.0);
+                setupLayerTexturing(texture);
+            }
+
+            const half3 solidColor = layer.source.solidColor;
+            const half4 color = half4(solidColor.r, solidColor.g, solidColor.b, layer.alpha);
+            // Buffer sources will have a black solid color ignored in the shader,
+            // so in that scenario the solid color passed here is arbitrary.
+            setupLayerBlending(usePremultipliedAlpha, isOpaque, disableTexture, color,
+                               layer.geometry.roundedCornersRadius);
+            if (layer.disableBlending) {
+                glDisable(GL_BLEND);
+            }
+            setSourceDataSpace(layer.sourceDataspace);
+
+            drawMesh(mesh);
+
+            // Cleanup if there's a buffer source
+            if (layer.source.buffer.buffer != nullptr) {
+                disableBlending();
+                setSourceY410BT2020(false);
+                disableTexturing();
+            }
+        }
+
+        if (drawFence != nullptr) {
+            *drawFence = flush();
+        }
+        // If flush failed or we don't support native fences, we need to force the
+        // gl command stream to be executed.
+        if (drawFence == nullptr || drawFence->get() < 0) {
+            bool success = finish();
+            if (!success) {
+                ALOGE("Failed to flush RenderEngine commands");
+                checkErrors();
+                // Chances are, something illegal happened (either the caller passed
+                // us bad parameters, or we messed up our shader generation).
+                return INVALID_OPERATION;
+            }
+        }
+
+        checkErrors();
+    }
     return NO_ERROR;
 }
 
@@ -1331,6 +1358,20 @@
     return (isInputHdrDataSpace || isOutputHdrDataSpace) && inputTransfer != outputTransfer;
 }
 
+bool GLESRenderEngine::isImageCachedForTesting(uint64_t bufferId) {
+    std::lock_guard<std::mutex> lock(mRenderingMutex);
+    const auto& cachedImage = mImageCache.find(bufferId);
+    return cachedImage != mImageCache.end();
+}
+
+bool GLESRenderEngine::isFramebufferImageCachedForTesting(uint64_t bufferId) {
+    std::lock_guard<std::mutex> lock(mRenderingMutex);
+    return std::any_of(mFramebufferImageCache.cbegin(), mFramebufferImageCache.cend(),
+                       [=](std::pair<uint64_t, EGLImageKHR> image) {
+                           return image.first == bufferId;
+                       });
+}
+
 // FlushTracer implementation
 GLESRenderEngine::FlushTracer::FlushTracer(GLESRenderEngine* engine) : mEngine(engine) {
     mThread = std::thread(&GLESRenderEngine::FlushTracer::loop, this);
diff --git a/libs/renderengine/gl/GLESRenderEngine.h b/libs/renderengine/gl/GLESRenderEngine.h
index 728882a..efb6ef0 100644
--- a/libs/renderengine/gl/GLESRenderEngine.h
+++ b/libs/renderengine/gl/GLESRenderEngine.h
@@ -56,7 +56,7 @@
                      EGLDisplay display, EGLConfig config, EGLContext ctxt, EGLSurface dummy,
                      EGLContext protectedContext, EGLSurface protectedDummy,
                      uint32_t imageCacheSize);
-    ~GLESRenderEngine() override;
+    ~GLESRenderEngine() override EXCLUDES(mRenderingMutex);
 
     std::unique_ptr<Framebuffer> createFramebuffer() override;
     std::unique_ptr<Image> createImage() override;
@@ -74,8 +74,9 @@
     void genTextures(size_t count, uint32_t* names) override;
     void deleteTextures(size_t count, uint32_t const* names) override;
     void bindExternalTextureImage(uint32_t texName, const Image& image) override;
-    status_t bindExternalTextureBuffer(uint32_t texName, sp<GraphicBuffer> buffer, sp<Fence> fence);
-    void unbindExternalTextureBuffer(uint64_t bufferId);
+    status_t bindExternalTextureBuffer(uint32_t texName, sp<GraphicBuffer> buffer, sp<Fence> fence)
+            EXCLUDES(mRenderingMutex);
+    void unbindExternalTextureBuffer(uint64_t bufferId) EXCLUDES(mRenderingMutex);
     status_t bindFrameBuffer(Framebuffer* framebuffer) override;
     void unbindFrameBuffer(Framebuffer* framebuffer) override;
     void checkErrors() const override;
@@ -85,7 +86,7 @@
     bool useProtectedContext(bool useProtectedContext) override;
     status_t drawLayers(const DisplaySettings& display, const std::vector<LayerSettings>& layers,
                         ANativeWindowBuffer* buffer, base::unique_fd&& bufferFence,
-                        base::unique_fd* drawFence) override;
+                        base::unique_fd* drawFence) EXCLUDES(mRenderingMutex) override;
 
     // internal to RenderEngine
     EGLDisplay getEGLDisplay() const { return mEGLDisplay; }
@@ -93,6 +94,12 @@
     // Creates an output image for rendering to
     EGLImageKHR createFramebufferImageIfNeeded(ANativeWindowBuffer* nativeBuffer, bool isProtected);
 
+    // Test-only methods
+    // Returns true iff mImageCache contains an image keyed by bufferId
+    bool isImageCachedForTesting(uint64_t bufferId) EXCLUDES(mRenderingMutex);
+    // Returns true iff mFramebufferImageCache contains an image keyed by bufferId
+    bool isFramebufferImageCachedForTesting(uint64_t bufferId) EXCLUDES(mRenderingMutex);
+
 protected:
     Framebuffer* getFramebufferForDrawing() override;
     void dump(std::string& result) override;
@@ -197,10 +204,17 @@
     const bool mUseColorManagement = false;
 
     // Cache of GL images that we'll store per GraphicBuffer ID
-    // TODO: Layer should call back on destruction instead to clean this up,
-    // as it has better system utilization at the potential expense of a
-    // more complicated interface.
-    std::unordered_map<uint64_t, std::unique_ptr<Image>> mImageCache;
+    std::unordered_map<uint64_t, std::unique_ptr<Image>> mImageCache GUARDED_BY(mRenderingMutex);
+    // Mutex guarding rendering operations, so that:
+    // 1. GL operations aren't interleaved, and
+    // 2. Internal state related to rendering that is potentially modified by
+    // multiple threads is guaranteed thread-safe.
+    std::mutex mRenderingMutex;
+
+    // See bindExternalTextureBuffer above, but requiring that mRenderingMutex
+    // is held.
+    status_t bindExternalTextureBufferLocked(uint32_t texName, sp<GraphicBuffer> buffer,
+                                             sp<Fence> fence) REQUIRES(mRenderingMutex);
 
     std::unique_ptr<Framebuffer> mDrawingBuffer;
 
diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp
index a2bbaff..8c93cf4 100644
--- a/libs/renderengine/tests/RenderEngineTest.cpp
+++ b/libs/renderengine/tests/RenderEngineTest.cpp
@@ -19,6 +19,7 @@
 #include <renderengine/RenderEngine.h>
 #include <sync/sync.h>
 #include <ui/PixelFormat.h>
+#include "../gl/GLESRenderEngine.h"
 
 constexpr int DEFAULT_DISPLAY_WIDTH = 128;
 constexpr int DEFAULT_DISPLAY_HEIGHT = 256;
@@ -27,6 +28,19 @@
 namespace android {
 
 struct RenderEngineTest : public ::testing::Test {
+    static void SetUpTestSuite() {
+        sRE = renderengine::gl::GLESRenderEngine::create(static_cast<int32_t>(
+                                                                 ui::PixelFormat::RGBA_8888),
+                                                         0, 1);
+    }
+
+    static void TearDownTestSuite() {
+        // The ordering here is important - sCurrentBuffer must live longer
+        // than RenderEngine to avoid a null reference on tear-down.
+        sRE = nullptr;
+        sCurrentBuffer = nullptr;
+    }
+
     static sp<GraphicBuffer> allocateDefaultBuffer() {
         return new GraphicBuffer(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT,
                                  HAL_PIXEL_FORMAT_RGBA_8888, 1,
@@ -101,12 +115,12 @@
                     DEFAULT_DISPLAY_HEIGHT - DEFAULT_DISPLAY_OFFSET);
     }
 
-    static void invokeDraw(renderengine::DisplaySettings settings,
-                           std::vector<renderengine::LayerSettings> layers,
-                           sp<GraphicBuffer> buffer) {
+    void invokeDraw(renderengine::DisplaySettings settings,
+                    std::vector<renderengine::LayerSettings> layers, sp<GraphicBuffer> buffer) {
         base::unique_fd fence;
         status_t status = sRE->drawLayers(settings, layers, buffer->getNativeBuffer(),
                                           base::unique_fd(), &fence);
+        sCurrentBuffer = buffer;
 
         int fd = fence.release();
         if (fd >= 0) {
@@ -115,9 +129,12 @@
         }
 
         ASSERT_EQ(NO_ERROR, status);
+        if (layers.size() > 0) {
+            ASSERT_TRUE(sRE->isFramebufferImageCachedForTesting(buffer->getId()));
+        }
     }
 
-    static void drawEmptyLayers() {
+    void drawEmptyLayers() {
         renderengine::DisplaySettings settings;
         std::vector<renderengine::LayerSettings> layers;
         // Meaningless buffer since we don't do any drawing
@@ -200,17 +217,22 @@
 
     void clearRegion();
 
-    // Dumb hack to get aroud the fact that tear-down for renderengine isn't
-    // well defined right now, so we can't create multiple instances
-    static std::unique_ptr<renderengine::RenderEngine> sRE;
+    // Keep around the same renderengine object to save on initialization time.
+    // For now, exercise the GL backend directly so that some caching specifics
+    // can be tested without changing the interface.
+    static std::unique_ptr<renderengine::gl::GLESRenderEngine> sRE;
+    // Dumb hack to avoid NPE in the EGL driver: the GraphicBuffer needs to
+    // be freed *after* RenderEngine is destroyed, so that the EGL image is
+    // destroyed first.
+    static sp<GraphicBuffer> sCurrentBuffer;
 
     sp<GraphicBuffer> mBuffer;
 
     std::vector<uint32_t> mTexNames;
 };
 
-std::unique_ptr<renderengine::RenderEngine> RenderEngineTest::sRE =
-        renderengine::RenderEngine::create(static_cast<int32_t>(ui::PixelFormat::RGBA_8888), 0, 1);
+std::unique_ptr<renderengine::gl::GLESRenderEngine> RenderEngineTest::sRE = nullptr;
+sp<GraphicBuffer> RenderEngineTest::sCurrentBuffer = nullptr;
 
 struct ColorSourceVariant {
     static void fillColor(renderengine::LayerSettings& layer, half r, half g, half b,
@@ -245,7 +267,7 @@
                           RenderEngineTest* fixture) {
         sp<GraphicBuffer> buf = RenderEngineTest::allocateSourceBuffer(1, 1);
         uint32_t texName;
-        RenderEngineTest::sRE->genTextures(1, &texName);
+        fixture->sRE->genTextures(1, &texName);
         fixture->mTexNames.push_back(texName);
 
         uint8_t* pixels;
@@ -740,6 +762,38 @@
     drawEmptyLayers();
 }
 
+TEST_F(RenderEngineTest, drawLayers_nullOutputBuffer) {
+    renderengine::DisplaySettings settings;
+    std::vector<renderengine::LayerSettings> layers;
+    renderengine::LayerSettings layer;
+    layer.geometry.boundaries = fullscreenRect().toFloatRect();
+    BufferSourceVariant<ForceOpaqueBufferVariant>::fillColor(layer, 1.0f, 0.0f, 0.0f, this);
+    layers.push_back(layer);
+    base::unique_fd fence;
+    status_t status = sRE->drawLayers(settings, layers, nullptr, base::unique_fd(), &fence);
+
+    ASSERT_EQ(BAD_VALUE, status);
+}
+
+TEST_F(RenderEngineTest, drawLayers_nullOutputFence) {
+    renderengine::DisplaySettings settings;
+    settings.physicalDisplay = fullscreenRect();
+    settings.clip = fullscreenRect();
+
+    std::vector<renderengine::LayerSettings> layers;
+    renderengine::LayerSettings layer;
+    layer.geometry.boundaries = fullscreenRect().toFloatRect();
+    BufferSourceVariant<ForceOpaqueBufferVariant>::fillColor(layer, 1.0f, 0.0f, 0.0f, this);
+    layer.alpha = 1.0;
+    layers.push_back(layer);
+
+    status_t status = sRE->drawLayers(settings, layers, mBuffer->getNativeBuffer(),
+                                      base::unique_fd(), nullptr);
+    sCurrentBuffer = mBuffer;
+    ASSERT_EQ(NO_ERROR, status);
+    expectBufferColor(fullscreenRect(), 255, 0, 0, 255);
+}
+
 TEST_F(RenderEngineTest, drawLayers_fillRedBuffer_colorSource) {
     fillRedBuffer<ColorSourceVariant>();
 }
@@ -912,4 +966,41 @@
     clearRegion();
 }
 
+TEST_F(RenderEngineTest, drawLayers_fillsBufferAndCachesImages) {
+    renderengine::DisplaySettings settings;
+    settings.physicalDisplay = fullscreenRect();
+    settings.clip = fullscreenRect();
+
+    std::vector<renderengine::LayerSettings> layers;
+
+    renderengine::LayerSettings layer;
+    layer.geometry.boundaries = fullscreenRect().toFloatRect();
+    BufferSourceVariant<ForceOpaqueBufferVariant>::fillColor(layer, 1.0f, 0.0f, 0.0f, this);
+
+    layers.push_back(layer);
+    invokeDraw(settings, layers, mBuffer);
+    uint64_t bufferId = layer.source.buffer.buffer->getId();
+    EXPECT_TRUE(sRE->isImageCachedForTesting(bufferId));
+    sRE->unbindExternalTextureBuffer(bufferId);
+    EXPECT_FALSE(sRE->isImageCachedForTesting(bufferId));
+}
+
+TEST_F(RenderEngineTest, drawLayers_bindExternalBufferWithNullBuffer) {
+    status_t result = sRE->bindExternalTextureBuffer(0, nullptr, nullptr);
+    ASSERT_EQ(BAD_VALUE, result);
+}
+
+TEST_F(RenderEngineTest, drawLayers_bindExternalBufferCachesImages) {
+    sp<GraphicBuffer> buf = allocateSourceBuffer(1, 1);
+    uint32_t texName;
+    sRE->genTextures(1, &texName);
+    mTexNames.push_back(texName);
+
+    sRE->bindExternalTextureBuffer(texName, buf, nullptr);
+    uint64_t bufferId = buf->getId();
+    EXPECT_TRUE(sRE->isImageCachedForTesting(bufferId));
+    sRE->unbindExternalTextureBuffer(bufferId);
+    EXPECT_FALSE(sRE->isImageCachedForTesting(bufferId));
+}
+
 } // namespace android
diff --git a/libs/ui/Transform.cpp b/libs/ui/Transform.cpp
index d13942d..28c3f7b 100644
--- a/libs/ui/Transform.cpp
+++ b/libs/ui/Transform.cpp
@@ -381,6 +381,37 @@
     return (getOrientation() & ROT_INVALID) ? false : true;
 }
 
+mat4 Transform::asMatrix4() const {
+    // Internally Transform uses a 3x3 matrix since the transform is meant for
+    // two-dimensional values. An equivalent 4x4 matrix means inserting an extra
+    // row and column which adds as an identity transform on the third
+    // dimension.
+
+    mat4 m = mat4{mat4::NO_INIT}; // NO_INIT since we explicitly set every element
+
+    m[0][0] = mMatrix[0][0];
+    m[0][1] = mMatrix[0][1];
+    m[0][2] = 0.f;
+    m[0][3] = mMatrix[0][2];
+
+    m[1][0] = mMatrix[1][0];
+    m[1][1] = mMatrix[1][1];
+    m[1][2] = 0.f;
+    m[1][3] = mMatrix[1][2];
+
+    m[2][0] = 0.f;
+    m[2][1] = 0.f;
+    m[2][2] = 1.f;
+    m[2][3] = 0.f;
+
+    m[3][0] = mMatrix[2][0];
+    m[3][1] = mMatrix[2][1];
+    m[3][2] = 0.f;
+    m[3][3] = mMatrix[2][2];
+
+    return m;
+}
+
 void Transform::dump(std::string& out, const char* name) const {
     using android::base::StringAppendF;
 
diff --git a/libs/ui/include/ui/Transform.h b/libs/ui/include/ui/Transform.h
index dcb26cf..f29a370 100644
--- a/libs/ui/include/ui/Transform.h
+++ b/libs/ui/include/ui/Transform.h
@@ -22,6 +22,7 @@
 #include <string>
 
 #include <hardware/hardware.h>
+#include <math/mat4.h>
 #include <math/vec2.h>
 #include <math/vec3.h>
 #include <ui/Point.h>
@@ -88,6 +89,9 @@
     vec2 transform(const vec2& v) const;
     vec3 transform(const vec3& v) const;
 
+    // Expands from the internal 3x3 matrix to an equivalent 4x4 matrix
+    mat4 asMatrix4() const;
+
     Transform inverse() const;
 
     // for debugging
diff --git a/services/surfaceflinger/BufferLayer.cpp b/services/surfaceflinger/BufferLayer.cpp
index bb18aa1..4751e5f 100644
--- a/services/surfaceflinger/BufferLayer.cpp
+++ b/services/surfaceflinger/BufferLayer.cpp
@@ -105,6 +105,10 @@
     return getEffectiveScalingMode() != NATIVE_WINDOW_SCALING_MODE_FREEZE;
 }
 
+bool BufferLayer::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);
diff --git a/services/surfaceflinger/BufferLayer.h b/services/surfaceflinger/BufferLayer.h
index 4f3ad41..dc103cb 100644
--- a/services/surfaceflinger/BufferLayer.h
+++ b/services/surfaceflinger/BufferLayer.h
@@ -78,6 +78,8 @@
     // isFixedSize - true if content has a fixed size
     bool isFixedSize() const override;
 
+    bool usesSourceCrop() const override;
+
     bool isHdrY410() const override;
 
     void setPerFrameData(const sp<const DisplayDevice>& display, const ui::Transform& transform,
diff --git a/services/surfaceflinger/ColorLayer.h b/services/surfaceflinger/ColorLayer.h
index bd83d1a..53d5b5b 100644
--- a/services/surfaceflinger/ColorLayer.h
+++ b/services/surfaceflinger/ColorLayer.h
@@ -46,11 +46,10 @@
     bool onPreComposition(nsecs_t /*refreshStartTime*/) override { return false; }
 
 protected:
-    FloatRect computeCrop(const sp<const DisplayDevice>& /*display*/) const override { return {}; }
-    bool prepareClientLayer(const RenderArea& renderArea, const Region& clip,
-                            bool useIdentityTransform, Region& clearRegion,
-                            const bool supportProtectedContent,
-                            renderengine::LayerSettings& layer) override;
+    virtual bool prepareClientLayer(const RenderArea& renderArea, const Region& clip,
+                                    bool useIdentityTransform, Region& clearRegion,
+                                    const bool supportProtectedContent,
+                                    renderengine::LayerSettings& layer);
 
 private:
     std::shared_ptr<compositionengine::Layer> mCompositionLayer;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
index 6cc87ba..9f635b9 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
@@ -24,12 +24,21 @@
 
 namespace compositionengine {
 
+struct LayerFECompositionState;
+
 // Defines the interface used by the CompositionEngine to make requests
 // of the front-end layer
 class LayerFE : public virtual RefBase {
 public:
+    // Latches the output-independent state. If includeGeometry is false, the
+    // geometry state can be skipped.
+    virtual void latchCompositionState(LayerFECompositionState&, bool includeGeometry) const = 0;
+
     // Called after the layer is displayed to update the presentation fence
     virtual void onLayerDisplayed(const sp<Fence>&) = 0;
+
+    // Gets some kind of identifier for the layer for debug purposes.
+    virtual const char* getDebugName() const = 0;
 };
 
 } // namespace compositionengine
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
index 785a6d7..e6ee078 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
@@ -41,6 +41,22 @@
     Region geomVisibleRegion;
 
     /*
+     * Geometry state
+     */
+
+    bool isSecure{false};
+    bool geomUsesSourceCrop{false};
+    bool geomBufferUsesDisplayInverseTransform{false};
+    uint32_t geomBufferTransform{0};
+    ui::Transform geomLayerTransform;
+    ui::Transform geomInverseLayerTransform;
+    Rect geomBufferSize;
+    Rect geomContentCrop;
+    Rect geomCrop;
+    Region geomActiveTransparentRegion;
+    FloatRect geomLayerBounds;
+
+    /*
      * Presentation
      */
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h
index e7a17c4..cd63b57 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h
@@ -64,6 +64,15 @@
     // TODO(lpique): Make this protected once it is only internally called.
     virtual CompositionState& editState() = 0;
 
+    // Recalculates the state of the output layer from the output-independent
+    // layer. If includeGeometry is false, the geometry state can be skipped.
+    virtual void updateCompositionState(bool includeGeometry) = 0;
+
+    // Writes the geometry state to the HWC, or does nothing if this layer does
+    // not use the HWC. If includeGeometry is false, the geometry state can be
+    // skipped.
+    virtual void writeStateToHWC(bool includeGeometry) const = 0;
+
     // Debugging
     virtual void dump(std::string& result) const = 0;
 };
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h
index d8f0cdd..6a4818f 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h
@@ -21,6 +21,8 @@
 
 #include <compositionengine/OutputLayer.h>
 #include <compositionengine/impl/OutputLayerCompositionState.h>
+#include <ui/FloatRect.h>
+#include <ui/Rect.h>
 
 #include "DisplayHardware/DisplayIdentification.h"
 
@@ -41,9 +43,18 @@
     const OutputLayerCompositionState& getState() const override;
     OutputLayerCompositionState& editState() override;
 
+    void updateCompositionState(bool) override;
+    void writeStateToHWC(bool) const override;
+
     void dump(std::string& result) const override;
 
+    virtual FloatRect calculateOutputSourceCrop() const;
+    virtual Rect calculateOutputDisplayFrame() const;
+    virtual uint32_t calculateOutputRelativeBufferTransform() const;
+
 private:
+    Rect calculateInitialCrop() const;
+
     const compositionengine::Output& mOutput;
     std::shared_ptr<compositionengine::Layer> mLayer;
     sp<compositionengine::LayerFE> mLayerFE;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
index a0c2a63..aab18db 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
@@ -17,6 +17,7 @@
 #pragma once
 
 #include <compositionengine/LayerFE.h>
+#include <compositionengine/LayerFECompositionState.h>
 #include <gmock/gmock.h>
 #include <ui/Fence.h>
 
@@ -29,7 +30,10 @@
     LayerFE();
     virtual ~LayerFE();
 
+    MOCK_CONST_METHOD2(latchCompositionState, void(LayerFECompositionState&, bool));
     MOCK_METHOD1(onLayerDisplayed, void(const sp<Fence>&));
+
+    MOCK_CONST_METHOD0(getDebugName, const char*());
 };
 
 } // namespace android::compositionengine::mock
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/OutputLayer.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/OutputLayer.h
index 54c7407..29cd08a 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/OutputLayer.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/OutputLayer.h
@@ -38,6 +38,9 @@
     MOCK_CONST_METHOD0(getState, const impl::OutputLayerCompositionState&());
     MOCK_METHOD0(editState, impl::OutputLayerCompositionState&());
 
+    MOCK_METHOD1(updateCompositionState, void(bool));
+    MOCK_CONST_METHOD1(writeStateToHWC, void(bool));
+
     MOCK_CONST_METHOD1(dump, void(std::string&));
 };
 
diff --git a/services/surfaceflinger/CompositionEngine/src/DumpHelpers.cpp b/services/surfaceflinger/CompositionEngine/src/DumpHelpers.cpp
index c497013..9598430 100644
--- a/services/surfaceflinger/CompositionEngine/src/DumpHelpers.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/DumpHelpers.cpp
@@ -24,7 +24,7 @@
 using android::base::StringAppendF;
 
 void dumpVal(std::string& out, const char* name, bool value) {
-    StringAppendF(&out, "%s=%c ", name, value ? 'T' : 'F');
+    StringAppendF(&out, "%s=%s ", name, value ? "true" : "false");
 }
 
 void dumpVal(std::string& out, const char* name, const void* value) {
@@ -56,7 +56,7 @@
 }
 
 void dumpVal(std::string& out, const char* name, const char* valueName, int value) {
-    StringAppendF(&out, "%s=%s (%d)", name, valueName, value);
+    StringAppendF(&out, "%s=%s (%d) ", name, valueName, value);
 }
 
 void dumpVal(std::string& out, const char* name, const std::string& valueName, int value) {
diff --git a/services/surfaceflinger/CompositionEngine/src/Layer.cpp b/services/surfaceflinger/CompositionEngine/src/Layer.cpp
index 109e9f8..96e9731 100644
--- a/services/surfaceflinger/CompositionEngine/src/Layer.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Layer.cpp
@@ -52,7 +52,9 @@
 }
 
 void Layer::dump(std::string& out) const {
-    android::base::StringAppendF(&out, "     Layer %p\n", this);
+    auto layerFE = getLayerFE();
+    android::base::StringAppendF(&out, "* compositionengine::Layer %p (%s)\n", this,
+                                 layerFE ? layerFE->getDebugName() : "<unknown>");
     mState.dump(out);
 }
 
diff --git a/services/surfaceflinger/CompositionEngine/src/LayerCompositionState.cpp b/services/surfaceflinger/CompositionEngine/src/LayerCompositionState.cpp
index 517b641..40c4da9 100644
--- a/services/surfaceflinger/CompositionEngine/src/LayerCompositionState.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/LayerCompositionState.cpp
@@ -31,6 +31,25 @@
 
 void dumpFrontEnd(std::string& out, const LayerFECompositionState& state) {
     out.append("      ");
+    dumpVal(out, "isSecure", state.isSecure);
+    dumpVal(out, "geomUsesSourceCrop", state.geomUsesSourceCrop);
+    dumpVal(out, "geomBufferUsesDisplayInverseTransform",
+            state.geomBufferUsesDisplayInverseTransform);
+    dumpVal(out, "geomLayerTransform", state.geomLayerTransform);
+
+    out.append("\n      ");
+    dumpVal(out, "geomBufferSize", state.geomBufferSize);
+    dumpVal(out, "geomContentCrop", state.geomContentCrop);
+    dumpVal(out, "geomCrop", state.geomCrop);
+    dumpVal(out, "geomBufferTransform", state.geomBufferTransform);
+
+    out.append("\n      ");
+    dumpVal(out, "geomActiveTransparentRegion", state.geomActiveTransparentRegion);
+
+    out.append("      ");
+    dumpVal(out, "geomLayerBounds", state.geomLayerBounds);
+
+    out.append("\n      ");
     dumpVal(out, "blend", toString(state.blendMode), state.blendMode);
     dumpVal(out, "alpha", state.alpha);
 
@@ -61,7 +80,7 @@
 } // namespace
 
 void LayerCompositionState::dump(std::string& out) const {
-    out.append("      frontend:\n");
+    out.append("    frontend:\n");
     dumpFrontEnd(out, frontEnd);
 }
 
diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp
index ad4c7bf..d22bdaf 100644
--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -147,7 +147,7 @@
         out.append("    No render surface!\n");
     }
 
-    out.append("\n   %d Layers", mOutputLayersOrderedByZ.size());
+    android::base::StringAppendF(&out, "\n   %zu Layers\b", mOutputLayersOrderedByZ.size());
     for (const auto& outputLayer : mOutputLayersOrderedByZ) {
         if (!outputLayer) {
             continue;
diff --git a/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp b/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp
index 78807ff..9549054 100644
--- a/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp
@@ -20,6 +20,7 @@
 namespace android::compositionengine::impl {
 
 void OutputCompositionState::dump(std::string& out) const {
+    out.append("   ");
     dumpVal(out, "isEnabled", isEnabled);
     dumpVal(out, "isSecure", isSecure);
 
@@ -37,7 +38,7 @@
     dumpVal(out, "scissor", scissor);
     dumpVal(out, "needsFiltering", needsFiltering);
 
-    out.append("\n");
+    out.append("\n   ");
 
     dumpVal(out, "colorMode", toString(colorMode), colorMode);
     dumpVal(out, "renderIntent", toString(renderIntent), renderIntent);
diff --git a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
index 10da49d..379ad87 100644
--- a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
@@ -19,7 +19,10 @@
 #include <compositionengine/Layer.h>
 #include <compositionengine/LayerFE.h>
 #include <compositionengine/Output.h>
+#include <compositionengine/impl/LayerCompositionState.h>
+#include <compositionengine/impl/OutputCompositionState.h>
 #include <compositionengine/impl/OutputLayer.h>
+#include <compositionengine/impl/OutputLayerCompositionState.h>
 
 #include "DisplayHardware/HWComposer.h"
 
@@ -29,6 +32,18 @@
 
 namespace impl {
 
+namespace {
+
+FloatRect reduce(const FloatRect& win, const Region& exclude) {
+    if (CC_LIKELY(exclude.isEmpty())) {
+        return win;
+    }
+    // Convert through Rect (by rounding) for lack of FloatRegion
+    return Region(Rect{win}).subtract(exclude).getBounds().toFloatRect();
+}
+
+} // namespace
+
 std::unique_ptr<compositionengine::OutputLayer> createOutputLayer(
         const CompositionEngine& compositionEngine, std::optional<DisplayId> displayId,
         const compositionengine::Output& output, std::shared_ptr<compositionengine::Layer> layer,
@@ -77,10 +92,296 @@
     return mState;
 }
 
+Rect OutputLayer::calculateInitialCrop() const {
+    const auto& layerState = mLayer->getState().frontEnd;
+
+    // apply the projection's clipping to the window crop in
+    // layerstack space, and convert-back to layer space.
+    // if there are no window scaling involved, this operation will map to full
+    // pixels in the buffer.
+
+    FloatRect activeCropFloat =
+            reduce(layerState.geomLayerBounds, layerState.geomActiveTransparentRegion);
+
+    const Rect& viewport = mOutput.getState().viewport;
+    const ui::Transform& layerTransform = layerState.geomLayerTransform;
+    const ui::Transform& inverseLayerTransform = layerState.geomInverseLayerTransform;
+    // Transform to screen space.
+    activeCropFloat = layerTransform.transform(activeCropFloat);
+    activeCropFloat = activeCropFloat.intersect(viewport.toFloatRect());
+    // Back to layer space to work with the content crop.
+    activeCropFloat = inverseLayerTransform.transform(activeCropFloat);
+
+    // This needs to be here as transform.transform(Rect) computes the
+    // transformed rect and then takes the bounding box of the result before
+    // returning. This means
+    // transform.inverse().transform(transform.transform(Rect)) != Rect
+    // in which case we need to make sure the final rect is clipped to the
+    // display bounds.
+    Rect activeCrop{activeCropFloat};
+    if (!activeCrop.intersect(layerState.geomBufferSize, &activeCrop)) {
+        activeCrop.clear();
+    }
+    return activeCrop;
+}
+
+FloatRect OutputLayer::calculateOutputSourceCrop() const {
+    const auto& layerState = mLayer->getState().frontEnd;
+    const auto& outputState = mOutput.getState();
+
+    if (!layerState.geomUsesSourceCrop) {
+        return {};
+    }
+
+    // the content crop is the area of the content that gets scaled to the
+    // layer's size. This is in buffer space.
+    FloatRect crop = layerState.geomContentCrop.toFloatRect();
+
+    // In addition there is a WM-specified crop we pull from our drawing state.
+    Rect activeCrop = calculateInitialCrop();
+    const Rect& bufferSize = layerState.geomBufferSize;
+
+    int winWidth = bufferSize.getWidth();
+    int winHeight = bufferSize.getHeight();
+
+    // The bufferSize for buffer state layers can be unbounded ([0, 0, -1, -1])
+    // if display frame hasn't been set and the parent is an unbounded layer.
+    if (winWidth < 0 && winHeight < 0) {
+        return crop;
+    }
+
+    // Transform the window crop to match the buffer coordinate system,
+    // which means using the inverse of the current transform set on the
+    // SurfaceFlingerConsumer.
+    uint32_t invTransform = layerState.geomBufferTransform;
+    if (layerState.geomBufferUsesDisplayInverseTransform) {
+        /*
+         * the code below applies the primary display's inverse transform to the
+         * buffer
+         */
+        uint32_t invTransformOrient = outputState.orientation;
+        // calculate the inverse transform
+        if (invTransformOrient & HAL_TRANSFORM_ROT_90) {
+            invTransformOrient ^= HAL_TRANSFORM_FLIP_V | HAL_TRANSFORM_FLIP_H;
+        }
+        // and apply to the current transform
+        invTransform =
+                (ui::Transform(invTransformOrient) * ui::Transform(invTransform)).getOrientation();
+    }
+
+    if (invTransform & HAL_TRANSFORM_ROT_90) {
+        // If the activeCrop has been rotate the ends are rotated but not
+        // the space itself so when transforming ends back we can't rely on
+        // a modification of the axes of rotation. To account for this we
+        // need to reorient the inverse rotation in terms of the current
+        // axes of rotation.
+        bool is_h_flipped = (invTransform & HAL_TRANSFORM_FLIP_H) != 0;
+        bool is_v_flipped = (invTransform & HAL_TRANSFORM_FLIP_V) != 0;
+        if (is_h_flipped == is_v_flipped) {
+            invTransform ^= HAL_TRANSFORM_FLIP_V | HAL_TRANSFORM_FLIP_H;
+        }
+        std::swap(winWidth, winHeight);
+    }
+    const Rect winCrop =
+            activeCrop.transform(invTransform, bufferSize.getWidth(), bufferSize.getHeight());
+
+    // below, crop is intersected with winCrop expressed in crop's coordinate space
+    float xScale = crop.getWidth() / float(winWidth);
+    float yScale = crop.getHeight() / float(winHeight);
+
+    float insetL = winCrop.left * xScale;
+    float insetT = winCrop.top * yScale;
+    float insetR = (winWidth - winCrop.right) * xScale;
+    float insetB = (winHeight - winCrop.bottom) * yScale;
+
+    crop.left += insetL;
+    crop.top += insetT;
+    crop.right -= insetR;
+    crop.bottom -= insetB;
+
+    return crop;
+}
+
+Rect OutputLayer::calculateOutputDisplayFrame() const {
+    const auto& layerState = mLayer->getState().frontEnd;
+    const auto& outputState = mOutput.getState();
+
+    // apply the layer's transform, followed by the display's global transform
+    // here we're guaranteed that the layer's transform preserves rects
+    Region activeTransparentRegion = layerState.geomActiveTransparentRegion;
+    const ui::Transform& layerTransform = layerState.geomLayerTransform;
+    const ui::Transform& inverseLayerTransform = layerState.geomInverseLayerTransform;
+    const Rect& bufferSize = layerState.geomBufferSize;
+    Rect activeCrop = layerState.geomCrop;
+    if (!activeCrop.isEmpty() && bufferSize.isValid()) {
+        activeCrop = layerTransform.transform(activeCrop);
+        if (!activeCrop.intersect(outputState.viewport, &activeCrop)) {
+            activeCrop.clear();
+        }
+        activeCrop = inverseLayerTransform.transform(activeCrop, true);
+        // This needs to be here as transform.transform(Rect) computes the
+        // transformed rect and then takes the bounding box of the result before
+        // returning. This means
+        // transform.inverse().transform(transform.transform(Rect)) != Rect
+        // in which case we need to make sure the final rect is clipped to the
+        // display bounds.
+        if (!activeCrop.intersect(bufferSize, &activeCrop)) {
+            activeCrop.clear();
+        }
+        // mark regions outside the crop as transparent
+        activeTransparentRegion.orSelf(Rect(0, 0, bufferSize.getWidth(), activeCrop.top));
+        activeTransparentRegion.orSelf(
+                Rect(0, activeCrop.bottom, bufferSize.getWidth(), bufferSize.getHeight()));
+        activeTransparentRegion.orSelf(Rect(0, activeCrop.top, activeCrop.left, activeCrop.bottom));
+        activeTransparentRegion.orSelf(
+                Rect(activeCrop.right, activeCrop.top, bufferSize.getWidth(), activeCrop.bottom));
+    }
+
+    // reduce uses a FloatRect to provide more accuracy during the
+    // transformation. We then round upon constructing 'frame'.
+    Rect frame{
+            layerTransform.transform(reduce(layerState.geomLayerBounds, activeTransparentRegion))};
+    if (!frame.intersect(outputState.viewport, &frame)) {
+        frame.clear();
+    }
+    const ui::Transform displayTransform{outputState.transform};
+
+    return displayTransform.transform(frame);
+}
+
+uint32_t OutputLayer::calculateOutputRelativeBufferTransform() const {
+    const auto& layerState = mLayer->getState().frontEnd;
+    const auto& outputState = mOutput.getState();
+
+    /*
+     * Transformations are applied in this order:
+     * 1) buffer orientation/flip/mirror
+     * 2) state transformation (window manager)
+     * 3) layer orientation (screen orientation)
+     * (NOTE: the matrices are multiplied in reverse order)
+     */
+    const ui::Transform& layerTransform = layerState.geomLayerTransform;
+    const ui::Transform displayTransform{outputState.orientation};
+    const ui::Transform bufferTransform{layerState.geomBufferTransform};
+    ui::Transform transform(displayTransform * layerTransform * bufferTransform);
+
+    if (layerState.geomBufferUsesDisplayInverseTransform) {
+        /*
+         * the code below applies the primary display's inverse transform to the
+         * buffer
+         */
+        uint32_t invTransform = outputState.orientation;
+        // calculate the inverse transform
+        if (invTransform & HAL_TRANSFORM_ROT_90) {
+            invTransform ^= HAL_TRANSFORM_FLIP_V | HAL_TRANSFORM_FLIP_H;
+        }
+
+        /*
+         * Here we cancel out the orientation component of the WM transform.
+         * The scaling and translate components are already included in our bounds
+         * computation so it's enough to just omit it in the composition.
+         * See comment in BufferLayer::prepareClientLayer with ref to b/36727915 for why.
+         */
+        transform =
+                ui::Transform(invTransform) * displayTransform * layerTransform * bufferTransform;
+    }
+
+    // this gives us only the "orientation" component of the transform
+    return transform.getOrientation();
+} // namespace impl
+
+void OutputLayer::updateCompositionState(bool includeGeometry) {
+    if (includeGeometry) {
+        mState.displayFrame = calculateOutputDisplayFrame();
+        mState.sourceCrop = calculateOutputSourceCrop();
+        mState.bufferTransform =
+                static_cast<Hwc2::Transform>(calculateOutputRelativeBufferTransform());
+
+        if ((mLayer->getState().frontEnd.isSecure && !mOutput.getState().isSecure) ||
+            (mState.bufferTransform & ui::Transform::ROT_INVALID)) {
+            mState.forceClientComposition = true;
+        }
+    }
+}
+
+void OutputLayer::writeStateToHWC(bool includeGeometry) const {
+    // Skip doing this if there is no HWC interface
+    if (!mState.hwc) {
+        return;
+    }
+
+    auto& hwcLayer = (*mState.hwc).hwcLayer;
+    if (!hwcLayer) {
+        ALOGE("[%s] failed to write composition state to HWC -- no hwcLayer for output %s",
+              mLayerFE->getDebugName(), mOutput.getName().c_str());
+        return;
+    }
+
+    if (includeGeometry) {
+        // Output dependent state
+
+        if (auto error = hwcLayer->setDisplayFrame(mState.displayFrame);
+            error != HWC2::Error::None) {
+            ALOGE("[%s] Failed to set display frame [%d, %d, %d, %d]: %s (%d)",
+                  mLayerFE->getDebugName(), mState.displayFrame.left, mState.displayFrame.top,
+                  mState.displayFrame.right, mState.displayFrame.bottom, to_string(error).c_str(),
+                  static_cast<int32_t>(error));
+        }
+
+        if (auto error = hwcLayer->setSourceCrop(mState.sourceCrop); error != HWC2::Error::None) {
+            ALOGE("[%s] Failed to set source crop [%.3f, %.3f, %.3f, %.3f]: "
+                  "%s (%d)",
+                  mLayerFE->getDebugName(), mState.sourceCrop.left, mState.sourceCrop.top,
+                  mState.sourceCrop.right, mState.sourceCrop.bottom, to_string(error).c_str(),
+                  static_cast<int32_t>(error));
+        }
+
+        if (auto error = hwcLayer->setZOrder(mState.z); error != HWC2::Error::None) {
+            ALOGE("[%s] Failed to set Z %u: %s (%d)", mLayerFE->getDebugName(), mState.z,
+                  to_string(error).c_str(), static_cast<int32_t>(error));
+        }
+
+        if (auto error =
+                    hwcLayer->setTransform(static_cast<HWC2::Transform>(mState.bufferTransform));
+            error != HWC2::Error::None) {
+            ALOGE("[%s] Failed to set transform %s: %s (%d)", mLayerFE->getDebugName(),
+                  toString(mState.bufferTransform).c_str(), to_string(error).c_str(),
+                  static_cast<int32_t>(error));
+        }
+
+        // Output independent state
+
+        const auto& outputIndependentState = mLayer->getState().frontEnd;
+
+        if (auto error = hwcLayer->setBlendMode(
+                    static_cast<HWC2::BlendMode>(outputIndependentState.blendMode));
+            error != HWC2::Error::None) {
+            ALOGE("[%s] Failed to set blend mode %s: %s (%d)", mLayerFE->getDebugName(),
+                  toString(outputIndependentState.blendMode).c_str(), to_string(error).c_str(),
+                  static_cast<int32_t>(error));
+        }
+
+        if (auto error = hwcLayer->setPlaneAlpha(outputIndependentState.alpha);
+            error != HWC2::Error::None) {
+            ALOGE("[%s] Failed to set plane alpha %.3f: %s (%d)", mLayerFE->getDebugName(),
+                  outputIndependentState.alpha, to_string(error).c_str(),
+                  static_cast<int32_t>(error));
+        }
+
+        if (auto error =
+                    hwcLayer->setInfo(outputIndependentState.type, outputIndependentState.appId);
+            error != HWC2::Error::None) {
+            ALOGE("[%s] Failed to set info %s (%d)", mLayerFE->getDebugName(),
+                  to_string(error).c_str(), static_cast<int32_t>(error));
+        }
+    }
+}
+
 void OutputLayer::dump(std::string& out) const {
     using android::base::StringAppendF;
 
-    StringAppendF(&out, "     Output Layer %p\n", this);
+    StringAppendF(&out, "  - Output Layer %p (Composition layer %p) (%s)\n", this, mLayer.get(),
+                  mLayerFE->getDebugName());
     mState.dump(out);
 }
 
diff --git a/services/surfaceflinger/CompositionEngine/src/OutputLayerCompositionState.cpp b/services/surfaceflinger/CompositionEngine/src/OutputLayerCompositionState.cpp
index 10f27b8..861ea57 100644
--- a/services/surfaceflinger/CompositionEngine/src/OutputLayerCompositionState.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/OutputLayerCompositionState.cpp
@@ -46,7 +46,7 @@
     dumpVal(out, "clearClientTarget", clearClientTarget);
     dumpVal(out, "displayFrame", displayFrame);
     dumpVal(out, "sourceCrop", sourceCrop);
-    dumpVal(out, "bufferTransform%", toString(bufferTransform), bufferTransform);
+    dumpVal(out, "bufferTransform", toString(bufferTransform), bufferTransform);
     dumpVal(out, "z-index", z);
 
     if (hwc) {
diff --git a/services/surfaceflinger/CompositionEngine/tests/FloatRectMatcher.h b/services/surfaceflinger/CompositionEngine/tests/FloatRectMatcher.h
new file mode 100644
index 0000000..6741cc9
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/tests/FloatRectMatcher.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <string>
+
+#include <android-base/stringprintf.h>
+#include <gmock/gmock.h>
+
+namespace {
+
+using android::base::StringAppendF;
+using FloatRect = android::FloatRect;
+
+void dumpFloatRect(const FloatRect& rect, std::string& result, const char* name) {
+    StringAppendF(&result, "%s (%f %f %f %f) ", name, rect.left, rect.top, rect.right, rect.bottom);
+}
+
+// Checks for a region match
+MATCHER_P(FloatRectEq, expected, "") {
+    std::string buf;
+    buf.append("FloatRects are not equal\n");
+    dumpFloatRect(expected, buf, "expected rect");
+    dumpFloatRect(arg, buf, "actual rect");
+    *result_listener << buf;
+
+    const float TOLERANCE = 1e-3f;
+    return (std::fabs(expected.left - arg.left) < TOLERANCE) &&
+            (std::fabs(expected.top - arg.top) < TOLERANCE) &&
+            (std::fabs(expected.right - arg.right) < TOLERANCE) &&
+            (std::fabs(expected.bottom - arg.bottom) < TOLERANCE);
+}
+
+} // namespace
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
index f7dcf6f..2504fa6 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
@@ -21,18 +21,40 @@
 #include <compositionengine/mock/Output.h>
 #include <gtest/gtest.h>
 
+#include "FloatRectMatcher.h"
 #include "MockHWC2.h"
 #include "MockHWComposer.h"
+#include "RectMatcher.h"
 
 namespace android::compositionengine {
 namespace {
 
+using testing::_;
+using testing::Return;
+using testing::ReturnRef;
 using testing::StrictMock;
 
 constexpr DisplayId DEFAULT_DISPLAY_ID = DisplayId{42};
 
+constexpr auto TR_IDENT = 0u;
+constexpr auto TR_FLP_H = HAL_TRANSFORM_FLIP_H;
+constexpr auto TR_FLP_V = HAL_TRANSFORM_FLIP_V;
+constexpr auto TR_ROT_90 = HAL_TRANSFORM_ROT_90;
+constexpr auto TR_ROT_180 = TR_FLP_H | TR_FLP_V;
+constexpr auto TR_ROT_270 = TR_ROT_90 | TR_ROT_180;
+
+const std::string kOutputName{"Test Output"};
+
 class OutputLayerTest : public testing::Test {
 public:
+    OutputLayerTest() {
+        EXPECT_CALL(*mLayerFE, getDebugName()).WillRepeatedly(Return("Test LayerFE"));
+        EXPECT_CALL(mOutput, getName()).WillRepeatedly(ReturnRef(kOutputName));
+
+        EXPECT_CALL(*mLayer, getState()).WillRepeatedly(ReturnRef(mLayerState));
+        EXPECT_CALL(mOutput, getState()).WillRepeatedly(ReturnRef(mOutputState));
+    }
+
     ~OutputLayerTest() override = default;
 
     compositionengine::mock::Output mOutput;
@@ -41,15 +63,18 @@
     sp<compositionengine::mock::LayerFE> mLayerFE{
             new StrictMock<compositionengine::mock::LayerFE>()};
     impl::OutputLayer mOutputLayer{mOutput, mLayer, mLayerFE};
+
+    impl::LayerCompositionState mLayerState;
+    impl::OutputCompositionState mOutputState;
 };
 
-/* ------------------------------------------------------------------------
+/*
  * Basic construction
  */
 
 TEST_F(OutputLayerTest, canInstantiateOutputLayer) {}
 
-/* ------------------------------------------------------------------------
+/*
  * OutputLayer::initialize()
  */
 
@@ -71,15 +96,481 @@
 
     mOutputLayer.initialize(compositionEngine, DEFAULT_DISPLAY_ID);
 
-    const auto& state = mOutputLayer.getState();
-    ASSERT_TRUE(state.hwc);
+    const auto& outputLayerState = mOutputLayer.getState();
+    ASSERT_TRUE(outputLayerState.hwc);
 
-    const auto& hwcState = *state.hwc;
+    const auto& hwcState = *outputLayerState.hwc;
     EXPECT_EQ(&hwcLayer, hwcState.hwcLayer.get());
 
     EXPECT_CALL(hwc, destroyLayer(DEFAULT_DISPLAY_ID, &hwcLayer));
     mOutputLayer.editState().hwc.reset();
 }
 
+/*
+ * OutputLayer::calculateOutputSourceCrop()
+ */
+
+struct OutputLayerSourceCropTest : public OutputLayerTest {
+    OutputLayerSourceCropTest() {
+        // Set reasonable default values for a simple case. Each test will
+        // set one specific value to something different.
+        mLayerState.frontEnd.geomUsesSourceCrop = true;
+        mLayerState.frontEnd.geomContentCrop = Rect{0, 0, 1920, 1080};
+        mLayerState.frontEnd.geomActiveTransparentRegion = Region{};
+        mLayerState.frontEnd.geomLayerBounds = FloatRect{0.f, 0.f, 1920.f, 1080.f};
+        mLayerState.frontEnd.geomLayerTransform = ui::Transform{TR_IDENT};
+        mLayerState.frontEnd.geomBufferSize = Rect{0, 0, 1920, 1080};
+        mLayerState.frontEnd.geomBufferTransform = TR_IDENT;
+
+        mOutputState.viewport = Rect{0, 0, 1920, 1080};
+    }
+
+    FloatRect calculateOutputSourceCrop() {
+        mLayerState.frontEnd.geomInverseLayerTransform =
+                mLayerState.frontEnd.geomLayerTransform.inverse();
+
+        return mOutputLayer.calculateOutputSourceCrop();
+    }
+};
+
+TEST_F(OutputLayerSourceCropTest, computesEmptyIfSourceCropNotUsed) {
+    mLayerState.frontEnd.geomUsesSourceCrop = false;
+
+    const FloatRect expected{};
+    EXPECT_THAT(calculateOutputSourceCrop(), FloatRectEq(expected));
+}
+
+TEST_F(OutputLayerSourceCropTest, correctForSimpleDefaultCase) {
+    const FloatRect expected{0.f, 0.f, 1920.f, 1080.f};
+    EXPECT_THAT(calculateOutputSourceCrop(), FloatRectEq(expected));
+}
+
+TEST_F(OutputLayerSourceCropTest, handlesBoundsOutsideViewport) {
+    mLayerState.frontEnd.geomLayerBounds = FloatRect{-2000.f, -2000.f, 2000.f, 2000.f};
+
+    const FloatRect expected{0.f, 0.f, 1920.f, 1080.f};
+    EXPECT_THAT(calculateOutputSourceCrop(), FloatRectEq(expected));
+}
+
+TEST_F(OutputLayerSourceCropTest, handlesBoundsOutsideViewportRotated) {
+    mLayerState.frontEnd.geomLayerBounds = FloatRect{-2000.f, -2000.f, 2000.f, 2000.f};
+    mLayerState.frontEnd.geomLayerTransform.set(HAL_TRANSFORM_ROT_90, 1920, 1080);
+
+    const FloatRect expected{0.f, 0.f, 1080.f, 1080.f};
+    EXPECT_THAT(calculateOutputSourceCrop(), FloatRectEq(expected));
+}
+
+TEST_F(OutputLayerSourceCropTest, calculateOutputSourceCropWorksWithATransformedBuffer) {
+    struct Entry {
+        uint32_t bufferInvDisplay;
+        uint32_t buffer;
+        uint32_t display;
+        FloatRect expected;
+    };
+    // Not an exhaustive list of cases, but hopefully enough.
+    const std::array<Entry, 12> testData = {
+            // clang-format off
+            //             inv      buffer      display     expected
+            /*  0 */ Entry{false,   TR_IDENT,   TR_IDENT,   FloatRect{0.f, 0.f, 1920.f, 1080.f}},
+            /*  1 */ Entry{false,   TR_IDENT,   TR_ROT_90,  FloatRect{0.f, 0.f, 1920.f, 1080.f}},
+            /*  2 */ Entry{false,   TR_IDENT,   TR_ROT_180, FloatRect{0.f, 0.f, 1920.f, 1080.f}},
+            /*  3 */ Entry{false,   TR_IDENT,   TR_ROT_270, FloatRect{0.f, 0.f, 1920.f, 1080.f}},
+
+            /*  4 */ Entry{true,    TR_IDENT,   TR_IDENT,   FloatRect{0.f, 0.f, 1920.f, 1080.f}},
+            /*  5 */ Entry{true,    TR_IDENT,   TR_ROT_90,  FloatRect{0.f, 0.f, 1920.f, 1080.f}},
+            /*  6 */ Entry{true,    TR_IDENT,   TR_ROT_180, FloatRect{0.f, 0.f, 1920.f, 1080.f}},
+            /*  7 */ Entry{true,    TR_IDENT,   TR_ROT_270, FloatRect{0.f, 0.f, 1920.f, 1080.f}},
+
+            /*  8 */ Entry{false,   TR_IDENT,   TR_IDENT,   FloatRect{0.f, 0.f, 1920.f, 1080.f}},
+            /*  9 */ Entry{false,   TR_ROT_90,  TR_ROT_90,  FloatRect{0.f, 0.f, 1920.f, 1080.f}},
+            /* 10 */ Entry{false,   TR_ROT_180, TR_ROT_180, FloatRect{0.f, 0.f, 1920.f, 1080.f}},
+            /* 11 */ Entry{false,   TR_ROT_270, TR_ROT_270, FloatRect{0.f, 0.f, 1920.f, 1080.f}},
+
+            // clang-format on
+    };
+
+    for (size_t i = 0; i < testData.size(); i++) {
+        const auto& entry = testData[i];
+
+        mLayerState.frontEnd.geomBufferUsesDisplayInverseTransform = entry.bufferInvDisplay;
+        mLayerState.frontEnd.geomBufferTransform = entry.buffer;
+        mOutputState.orientation = entry.display;
+
+        EXPECT_THAT(calculateOutputSourceCrop(), FloatRectEq(entry.expected)) << "entry " << i;
+    }
+}
+
+TEST_F(OutputLayerSourceCropTest, geomContentCropAffectsCrop) {
+    mLayerState.frontEnd.geomContentCrop = Rect{0, 0, 960, 540};
+
+    const FloatRect expected{0.f, 0.f, 960.f, 540.f};
+    EXPECT_THAT(calculateOutputSourceCrop(), FloatRectEq(expected));
+}
+
+TEST_F(OutputLayerSourceCropTest, viewportAffectsCrop) {
+    mOutputState.viewport = Rect{0, 0, 960, 540};
+
+    const FloatRect expected{0.f, 0.f, 960.f, 540.f};
+    EXPECT_THAT(calculateOutputSourceCrop(), FloatRectEq(expected));
+}
+
+/*
+ * OutputLayer::calculateOutputDisplayFrame()
+ */
+
+struct OutputLayerDisplayFrameTest : public OutputLayerTest {
+    OutputLayerDisplayFrameTest() {
+        // Set reasonable default values for a simple case. Each test will
+        // set one specific value to something different.
+
+        mLayerState.frontEnd.geomActiveTransparentRegion = Region{};
+        mLayerState.frontEnd.geomLayerTransform = ui::Transform{TR_IDENT};
+        mLayerState.frontEnd.geomBufferSize = Rect{0, 0, 1920, 1080};
+        mLayerState.frontEnd.geomBufferUsesDisplayInverseTransform = false;
+        mLayerState.frontEnd.geomCrop = Rect{0, 0, 1920, 1080};
+        mLayerState.frontEnd.geomLayerBounds = FloatRect{0.f, 0.f, 1920.f, 1080.f};
+
+        mOutputState.viewport = Rect{0, 0, 1920, 1080};
+        mOutputState.transform = ui::Transform{TR_IDENT};
+    }
+
+    Rect calculateOutputDisplayFrame() {
+        mLayerState.frontEnd.geomInverseLayerTransform =
+                mLayerState.frontEnd.geomLayerTransform.inverse();
+
+        return mOutputLayer.calculateOutputDisplayFrame();
+    }
+};
+
+TEST_F(OutputLayerDisplayFrameTest, correctForSimpleDefaultCase) {
+    const Rect expected{0, 0, 1920, 1080};
+    EXPECT_THAT(calculateOutputDisplayFrame(), RectEq(expected));
+}
+
+TEST_F(OutputLayerDisplayFrameTest, fullActiveTransparentRegionReturnsEmptyFrame) {
+    mLayerState.frontEnd.geomActiveTransparentRegion = Region{Rect{0, 0, 1920, 1080}};
+    const Rect expected{0, 0, 0, 0};
+    EXPECT_THAT(calculateOutputDisplayFrame(), RectEq(expected));
+}
+
+TEST_F(OutputLayerDisplayFrameTest, cropAffectsDisplayFrame) {
+    mLayerState.frontEnd.geomCrop = Rect{100, 200, 300, 500};
+    const Rect expected{100, 200, 300, 500};
+    EXPECT_THAT(calculateOutputDisplayFrame(), RectEq(expected));
+}
+
+TEST_F(OutputLayerDisplayFrameTest, cropAffectsDisplayFrameRotated) {
+    mLayerState.frontEnd.geomCrop = Rect{100, 200, 300, 500};
+    mLayerState.frontEnd.geomLayerTransform.set(HAL_TRANSFORM_ROT_90, 1920, 1080);
+    const Rect expected{1420, 100, 1720, 300};
+    EXPECT_THAT(calculateOutputDisplayFrame(), RectEq(expected));
+}
+
+TEST_F(OutputLayerDisplayFrameTest, emptyGeomCropIsNotUsedToComputeFrame) {
+    mLayerState.frontEnd.geomCrop = Rect{};
+    const Rect expected{0, 0, 1920, 1080};
+    EXPECT_THAT(calculateOutputDisplayFrame(), RectEq(expected));
+}
+
+TEST_F(OutputLayerDisplayFrameTest, geomLayerBoundsAffectsFrame) {
+    mLayerState.frontEnd.geomLayerBounds = FloatRect{0.f, 0.f, 960.f, 540.f};
+    const Rect expected{0, 0, 960, 540};
+    EXPECT_THAT(calculateOutputDisplayFrame(), RectEq(expected));
+}
+
+TEST_F(OutputLayerDisplayFrameTest, viewportAffectsFrame) {
+    mOutputState.viewport = Rect{0, 0, 960, 540};
+    const Rect expected{0, 0, 960, 540};
+    EXPECT_THAT(calculateOutputDisplayFrame(), RectEq(expected));
+}
+
+TEST_F(OutputLayerDisplayFrameTest, outputTransformAffectsDisplayFrame) {
+    mOutputState.transform = ui::Transform{HAL_TRANSFORM_ROT_90};
+    const Rect expected{-1080, 0, 0, 1920};
+    EXPECT_THAT(calculateOutputDisplayFrame(), RectEq(expected));
+}
+
+/*
+ * OutputLayer::calculateOutputRelativeBufferTransform()
+ */
+
+TEST_F(OutputLayerTest, calculateOutputRelativeBufferTransformTestsNeeded) {
+    mLayerState.frontEnd.geomBufferUsesDisplayInverseTransform = false;
+
+    struct Entry {
+        uint32_t layer;
+        uint32_t buffer;
+        uint32_t display;
+        uint32_t expected;
+    };
+    // Not an exhaustive list of cases, but hopefully enough.
+    const std::array<Entry, 24> testData = {
+            // clang-format off
+            //             layer       buffer      display     expected
+            /*  0 */ Entry{TR_IDENT,   TR_IDENT,   TR_IDENT,   TR_IDENT},
+            /*  1 */ Entry{TR_IDENT,   TR_IDENT,   TR_ROT_90,  TR_ROT_90},
+            /*  2 */ Entry{TR_IDENT,   TR_IDENT,   TR_ROT_180, TR_ROT_180},
+            /*  3 */ Entry{TR_IDENT,   TR_IDENT,   TR_ROT_270, TR_ROT_270},
+
+            /*  4 */ Entry{TR_IDENT,   TR_FLP_H,   TR_IDENT,   TR_FLP_H ^ TR_IDENT},
+            /*  5 */ Entry{TR_IDENT,   TR_FLP_H,   TR_ROT_90,  TR_FLP_H ^ TR_ROT_90},
+            /*  6 */ Entry{TR_IDENT,   TR_FLP_H,   TR_ROT_180, TR_FLP_H ^ TR_ROT_180},
+            /*  7 */ Entry{TR_IDENT,   TR_FLP_H,   TR_ROT_270, TR_FLP_H ^ TR_ROT_270},
+
+            /*  8 */ Entry{TR_IDENT,   TR_FLP_V,   TR_IDENT,   TR_FLP_V},
+            /*  9 */ Entry{TR_IDENT,   TR_ROT_90,  TR_ROT_90,  TR_ROT_180},
+            /* 10 */ Entry{TR_IDENT,   TR_ROT_180, TR_ROT_180, TR_IDENT},
+            /* 11 */ Entry{TR_IDENT,   TR_ROT_270, TR_ROT_270, TR_ROT_180},
+
+            /* 12 */ Entry{TR_ROT_90,  TR_IDENT,   TR_IDENT,   TR_IDENT ^ TR_ROT_90},
+            /* 13 */ Entry{TR_ROT_90,  TR_FLP_H,   TR_ROT_90,  TR_FLP_H ^ TR_ROT_180},
+            /* 14 */ Entry{TR_ROT_90,  TR_IDENT,   TR_ROT_180, TR_IDENT ^ TR_ROT_270},
+            /* 15 */ Entry{TR_ROT_90,  TR_FLP_H,   TR_ROT_270, TR_FLP_H ^ TR_IDENT},
+
+            /* 16 */ Entry{TR_ROT_180, TR_FLP_H,   TR_IDENT,   TR_FLP_H ^ TR_ROT_180},
+            /* 17 */ Entry{TR_ROT_180, TR_IDENT,   TR_ROT_90,  TR_IDENT ^ TR_ROT_270},
+            /* 18 */ Entry{TR_ROT_180, TR_FLP_H,   TR_ROT_180, TR_FLP_H ^ TR_IDENT},
+            /* 19 */ Entry{TR_ROT_180, TR_IDENT,   TR_ROT_270, TR_IDENT ^ TR_ROT_90},
+
+            /* 20 */ Entry{TR_ROT_270, TR_IDENT,   TR_IDENT,   TR_IDENT ^ TR_ROT_270},
+            /* 21 */ Entry{TR_ROT_270, TR_FLP_H,   TR_ROT_90,  TR_FLP_H ^ TR_IDENT},
+            /* 22 */ Entry{TR_ROT_270, TR_FLP_H,   TR_ROT_180, TR_FLP_H ^ TR_ROT_90},
+            /* 23 */ Entry{TR_ROT_270, TR_IDENT,   TR_ROT_270, TR_IDENT ^ TR_ROT_180},
+            // clang-format on
+    };
+
+    for (size_t i = 0; i < testData.size(); i++) {
+        const auto& entry = testData[i];
+
+        mLayerState.frontEnd.geomLayerTransform.set(entry.layer, 1920, 1080);
+        mLayerState.frontEnd.geomBufferTransform = entry.buffer;
+        mOutputState.orientation = entry.display;
+
+        auto actual = mOutputLayer.calculateOutputRelativeBufferTransform();
+        EXPECT_EQ(entry.expected, actual) << "entry " << i;
+    }
+}
+
+TEST_F(OutputLayerTest,
+       calculateOutputRelativeBufferTransformTestWithOfBufferUsesDisplayInverseTransform) {
+    mLayerState.frontEnd.geomBufferUsesDisplayInverseTransform = true;
+
+    struct Entry {
+        uint32_t layer;
+        uint32_t buffer;
+        uint32_t display;
+        uint32_t expected;
+    };
+    // Not an exhaustive list of cases, but hopefully enough.
+    const std::array<Entry, 24> testData = {
+            // clang-format off
+            //             layer       buffer      display     expected
+            /*  0 */ Entry{TR_IDENT,   TR_IDENT,   TR_IDENT,   TR_IDENT},
+            /*  1 */ Entry{TR_IDENT,   TR_IDENT,   TR_ROT_90,  TR_IDENT},
+            /*  2 */ Entry{TR_IDENT,   TR_IDENT,   TR_ROT_180, TR_IDENT},
+            /*  3 */ Entry{TR_IDENT,   TR_IDENT,   TR_ROT_270, TR_IDENT},
+
+            /*  4 */ Entry{TR_IDENT,   TR_FLP_H,   TR_IDENT,   TR_FLP_H},
+            /*  5 */ Entry{TR_IDENT,   TR_FLP_H,   TR_ROT_90,  TR_FLP_H},
+            /*  6 */ Entry{TR_IDENT,   TR_FLP_H,   TR_ROT_180, TR_FLP_H},
+            /*  7 */ Entry{TR_IDENT,   TR_FLP_H,   TR_ROT_270, TR_FLP_H},
+
+            /*  8 */ Entry{TR_IDENT,   TR_FLP_V,   TR_IDENT,   TR_FLP_V},
+            /*  9 */ Entry{TR_IDENT,   TR_ROT_90,  TR_ROT_90,  TR_ROT_90},
+            /* 10 */ Entry{TR_IDENT,   TR_ROT_180, TR_ROT_180, TR_ROT_180},
+            /* 11 */ Entry{TR_IDENT,   TR_ROT_270, TR_ROT_270, TR_ROT_270},
+
+            /* 12 */ Entry{TR_ROT_90,  TR_IDENT,   TR_IDENT,   TR_IDENT ^ TR_ROT_90},
+            /* 13 */ Entry{TR_ROT_90,  TR_FLP_H,   TR_ROT_90,  TR_FLP_H ^ TR_ROT_90},
+            /* 14 */ Entry{TR_ROT_90,  TR_IDENT,   TR_ROT_180, TR_IDENT ^ TR_ROT_90},
+            /* 15 */ Entry{TR_ROT_90,  TR_FLP_H,   TR_ROT_270, TR_FLP_H ^ TR_ROT_90},
+
+            /* 16 */ Entry{TR_ROT_180, TR_FLP_H,   TR_IDENT,   TR_FLP_H ^ TR_ROT_180},
+            /* 17 */ Entry{TR_ROT_180, TR_IDENT,   TR_ROT_90,  TR_IDENT ^ TR_ROT_180},
+            /* 18 */ Entry{TR_ROT_180, TR_FLP_H,   TR_ROT_180, TR_FLP_H ^ TR_ROT_180},
+            /* 19 */ Entry{TR_ROT_180, TR_IDENT,   TR_ROT_270, TR_IDENT ^ TR_ROT_180},
+
+            /* 20 */ Entry{TR_ROT_270, TR_IDENT,   TR_IDENT,   TR_IDENT ^ TR_ROT_270},
+            /* 21 */ Entry{TR_ROT_270, TR_FLP_H,   TR_ROT_90,  TR_FLP_H ^ TR_ROT_270},
+            /* 22 */ Entry{TR_ROT_270, TR_FLP_H,   TR_ROT_180, TR_FLP_H ^ TR_ROT_270},
+            /* 23 */ Entry{TR_ROT_270, TR_IDENT,   TR_ROT_270, TR_IDENT ^ TR_ROT_270},
+            // clang-format on
+    };
+
+    for (size_t i = 0; i < testData.size(); i++) {
+        const auto& entry = testData[i];
+
+        mLayerState.frontEnd.geomLayerTransform = ui::Transform{entry.layer};
+        mLayerState.frontEnd.geomBufferTransform = entry.buffer;
+        mOutputState.orientation = entry.display;
+
+        auto actual = mOutputLayer.calculateOutputRelativeBufferTransform();
+        EXPECT_EQ(entry.expected, actual) << "entry " << i;
+    }
+}
+
+/*
+ * OutputLayer::updateCompositionState()
+ */
+
+struct OutputLayerPartialMockForUpdateCompositionState : public impl::OutputLayer {
+    OutputLayerPartialMockForUpdateCompositionState(const compositionengine::Output& output,
+                                                    std::shared_ptr<compositionengine::Layer> layer,
+                                                    sp<compositionengine::LayerFE> layerFE)
+          : impl::OutputLayer(output, layer, layerFE) {}
+    // Mock everything called by updateCompositionState to simplify testing it.
+    MOCK_CONST_METHOD0(calculateOutputSourceCrop, FloatRect());
+    MOCK_CONST_METHOD0(calculateOutputDisplayFrame, Rect());
+    MOCK_CONST_METHOD0(calculateOutputRelativeBufferTransform, uint32_t());
+};
+
+struct OutputLayerUpdateCompositionStateTest : public OutputLayerTest {
+public:
+    OutputLayerUpdateCompositionStateTest() {
+        EXPECT_CALL(*mLayer, getState()).WillRepeatedly(ReturnRef(mLayerState));
+        EXPECT_CALL(mOutput, getState()).WillRepeatedly(ReturnRef(mOutputState));
+    }
+
+    ~OutputLayerUpdateCompositionStateTest() = default;
+
+    void setupGeometryChildCallValues() {
+        EXPECT_CALL(mOutputLayer, calculateOutputSourceCrop()).WillOnce(Return(kSourceCrop));
+        EXPECT_CALL(mOutputLayer, calculateOutputDisplayFrame()).WillOnce(Return(kDisplayFrame));
+        EXPECT_CALL(mOutputLayer, calculateOutputRelativeBufferTransform())
+                .WillOnce(Return(mBufferTransform));
+    }
+
+    void validateComputedGeometryState() {
+        const auto& state = mOutputLayer.getState();
+        EXPECT_EQ(kSourceCrop, state.sourceCrop);
+        EXPECT_EQ(kDisplayFrame, state.displayFrame);
+        EXPECT_EQ(static_cast<Hwc2::Transform>(mBufferTransform), state.bufferTransform);
+    }
+
+    const FloatRect kSourceCrop{1.f, 2.f, 3.f, 4.f};
+    const Rect kDisplayFrame{11, 12, 13, 14};
+    uint32_t mBufferTransform{21};
+
+    using OutputLayer = OutputLayerPartialMockForUpdateCompositionState;
+    StrictMock<OutputLayer> mOutputLayer{mOutput, mLayer, mLayerFE};
+};
+
+TEST_F(OutputLayerUpdateCompositionStateTest, setsStateNormally) {
+    mLayerState.frontEnd.isSecure = true;
+    mOutputState.isSecure = true;
+
+    setupGeometryChildCallValues();
+
+    mOutputLayer.updateCompositionState(true);
+
+    validateComputedGeometryState();
+
+    EXPECT_EQ(false, mOutputLayer.getState().forceClientComposition);
+}
+
+TEST_F(OutputLayerUpdateCompositionStateTest,
+       alsoSetsForceCompositionIfSecureLayerOnNonsecureOutput) {
+    mLayerState.frontEnd.isSecure = true;
+    mOutputState.isSecure = false;
+
+    setupGeometryChildCallValues();
+
+    mOutputLayer.updateCompositionState(true);
+
+    validateComputedGeometryState();
+
+    EXPECT_EQ(true, mOutputLayer.getState().forceClientComposition);
+}
+
+TEST_F(OutputLayerUpdateCompositionStateTest,
+       alsoSetsForceCompositionIfUnsupportedBufferTransform) {
+    mLayerState.frontEnd.isSecure = true;
+    mOutputState.isSecure = true;
+
+    mBufferTransform = ui::Transform::ROT_INVALID;
+
+    setupGeometryChildCallValues();
+
+    mOutputLayer.updateCompositionState(true);
+
+    validateComputedGeometryState();
+
+    EXPECT_EQ(true, mOutputLayer.getState().forceClientComposition);
+}
+
+TEST_F(OutputLayerUpdateCompositionStateTest, doesNotRecomputeGeometryIfNotRequested) {
+    mOutputLayer.updateCompositionState(false);
+
+    EXPECT_EQ(false, mOutputLayer.getState().forceClientComposition);
+}
+
+/*
+ * OutputLayer::writeStateToHWC()
+ */
+
+struct OutputLayerWriteStateToHWCTest : public OutputLayerTest {
+    static constexpr HWC2::Error kError = HWC2::Error::Unsupported;
+    static constexpr FloatRect kSourceCrop{11.f, 12.f, 13.f, 14.f};
+    static constexpr uint32_t kZOrder = 21u;
+    static constexpr Hwc2::Transform kBufferTransform = static_cast<Hwc2::Transform>(31);
+    static constexpr Hwc2::IComposerClient::BlendMode kBlendMode =
+            static_cast<Hwc2::IComposerClient::BlendMode>(41);
+    static constexpr float kAlpha = 51.f;
+    static constexpr uint32_t kType = 61u;
+    static constexpr uint32_t kAppId = 62u;
+
+    static const Rect kDisplayFrame;
+
+    OutputLayerWriteStateToHWCTest() {
+        auto& outputLayerState = mOutputLayer.editState();
+        outputLayerState.hwc = impl::OutputLayerCompositionState::Hwc(mHwcLayer);
+
+        outputLayerState.displayFrame = kDisplayFrame;
+        outputLayerState.sourceCrop = kSourceCrop;
+        outputLayerState.z = kZOrder;
+        outputLayerState.bufferTransform = static_cast<Hwc2::Transform>(kBufferTransform);
+
+        mLayerState.frontEnd.blendMode = kBlendMode;
+        mLayerState.frontEnd.alpha = kAlpha;
+        mLayerState.frontEnd.type = kType;
+        mLayerState.frontEnd.appId = kAppId;
+    }
+
+    void expectGeometryCommonCalls() {
+        EXPECT_CALL(*mHwcLayer, setDisplayFrame(kDisplayFrame)).WillOnce(Return(kError));
+        EXPECT_CALL(*mHwcLayer, setSourceCrop(kSourceCrop)).WillOnce(Return(kError));
+        EXPECT_CALL(*mHwcLayer, setZOrder(kZOrder)).WillOnce(Return(kError));
+        EXPECT_CALL(*mHwcLayer, setTransform(static_cast<HWC2::Transform>(kBufferTransform)))
+                .WillOnce(Return(kError));
+
+        EXPECT_CALL(*mHwcLayer, setBlendMode(static_cast<HWC2::BlendMode>(kBlendMode)))
+                .WillOnce(Return(kError));
+        EXPECT_CALL(*mHwcLayer, setPlaneAlpha(kAlpha)).WillOnce(Return(kError));
+        EXPECT_CALL(*mHwcLayer, setInfo(kType, kAppId)).WillOnce(Return(kError));
+    }
+
+    std::shared_ptr<HWC2::mock::Layer> mHwcLayer{std::make_shared<StrictMock<HWC2::mock::Layer>>()};
+};
+
+const Rect OutputLayerWriteStateToHWCTest::kDisplayFrame{1001, 1002, 1003, 10044};
+
+TEST_F(OutputLayerWriteStateToHWCTest, doesNothingIfNoHWCState) {
+    mOutputLayer.editState().hwc.reset();
+
+    mOutputLayer.writeStateToHWC(true);
+}
+
+TEST_F(OutputLayerWriteStateToHWCTest, doesNothingIfNoHWCLayer) {
+    mOutputLayer.editState().hwc = impl::OutputLayerCompositionState::Hwc(nullptr);
+
+    mOutputLayer.writeStateToHWC(true);
+}
+
+TEST_F(OutputLayerWriteStateToHWCTest, canSetsAllState) {
+    expectGeometryCommonCalls();
+
+    mOutputLayer.writeStateToHWC(true);
+}
+
 } // namespace
 } // namespace android::compositionengine
diff --git a/services/surfaceflinger/CompositionEngine/tests/RectMatcher.h b/services/surfaceflinger/CompositionEngine/tests/RectMatcher.h
new file mode 100644
index 0000000..d4c76bc
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/tests/RectMatcher.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <string>
+
+#include <android-base/stringprintf.h>
+#include <gmock/gmock.h>
+
+namespace {
+
+using android::base::StringAppendF;
+using Rect = android::Rect;
+
+void dumpRect(const Rect& rect, std::string& result, const char* name) {
+    StringAppendF(&result, "%s (%d %d %d %d) ", name, rect.left, rect.top, rect.right, rect.bottom);
+}
+
+// Checks for a region match
+MATCHER_P(RectEq, expected, "") {
+    std::string buf;
+    buf.append("Rects are not equal\n");
+    dumpRect(expected, buf, "expected rect");
+    dumpRect(arg, buf, "actual rect");
+    *result_listener << buf;
+
+    return (expected.left == arg.left) && (expected.top == arg.top) &&
+            (expected.right == arg.right) && (expected.bottom == arg.bottom);
+}
+
+} // namespace
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index d28a484..512a0b4 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -29,6 +29,7 @@
 #include <android-base/stringprintf.h>
 #include <compositionengine/Display.h>
 #include <compositionengine/Layer.h>
+#include <compositionengine/LayerFECompositionState.h>
 #include <compositionengine/OutputLayer.h>
 #include <compositionengine/impl/LayerCompositionState.h>
 #include <compositionengine/impl/OutputLayerCompositionState.h>
@@ -368,36 +369,6 @@
     return size;
 }
 
-Rect Layer::computeInitialCrop(const sp<const DisplayDevice>& display) const {
-    // the crop is the area of the window that gets cropped, but not
-    // scaled in any ways.
-    const State& s(getDrawingState());
-
-    // apply the projection's clipping to the window crop in
-    // layerstack space, and convert-back to layer space.
-    // if there are no window scaling involved, this operation will map to full
-    // pixels in the buffer.
-
-    FloatRect activeCropFloat = getBounds();
-    ui::Transform t = getTransform();
-    // Transform to screen space.
-    activeCropFloat = t.transform(activeCropFloat);
-    activeCropFloat = activeCropFloat.intersect(display->getViewport().toFloatRect());
-    // Back to layer space to work with the content crop.
-    activeCropFloat = t.inverse().transform(activeCropFloat);
-    // This needs to be here as transform.transform(Rect) computes the
-    // transformed rect and then takes the bounding box of the result before
-    // returning. This means
-    // transform.inverse().transform(transform.transform(Rect)) != Rect
-    // in which case we need to make sure the final rect is clipped to the
-    // display bounds.
-    Rect activeCrop{activeCropFloat};
-    if (!activeCrop.intersect(getBufferSize(s), &activeCrop)) {
-        activeCrop.clear();
-    }
-    return activeCrop;
-}
-
 void Layer::setupRoundedCornersCropCoordinates(Rect win,
                                                const FloatRect& roundedCornersCrop) const {
     // Translate win by the rounded corners rect coordinates, to have all values in
@@ -415,189 +386,17 @@
     cropCoords[3] = vec2(win.right, win.top);
 }
 
-FloatRect Layer::computeCrop(const sp<const DisplayDevice>& display) const {
-    // the content crop is the area of the content that gets scaled to the
-    // layer's size. This is in buffer space.
-    FloatRect crop = getContentCrop().toFloatRect();
-
-    // In addition there is a WM-specified crop we pull from our drawing state.
-    const State& s(getDrawingState());
-
-    Rect activeCrop = computeInitialCrop(display);
-    Rect bufferSize = getBufferSize(s);
-
-    int32_t winWidth = bufferSize.getWidth();
-    int32_t winHeight = bufferSize.getHeight();
-
-    // The bufferSize for buffer state layers can be unbounded ([0, 0, -1, -1]) if display frame
-    // hasn't been set and the parent is an unbounded layer.
-    if (winWidth < 0 && winHeight < 0) {
-        return crop;
-    }
-
-    // Transform the window crop to match the buffer coordinate system,
-    // which means using the inverse of the current transform set on the
-    // SurfaceFlingerConsumer.
-    uint32_t invTransform = mCurrentTransform;
-    if (getTransformToDisplayInverse()) {
-        /*
-         * the code below applies the primary display's inverse transform to the
-         * buffer
-         */
-        uint32_t invTransformOrient = DisplayDevice::getPrimaryDisplayOrientationTransform();
-        // calculate the inverse transform
-        if (invTransformOrient & NATIVE_WINDOW_TRANSFORM_ROT_90) {
-            invTransformOrient ^= NATIVE_WINDOW_TRANSFORM_FLIP_V | NATIVE_WINDOW_TRANSFORM_FLIP_H;
-        }
-        // and apply to the current transform
-        invTransform = (ui::Transform(invTransformOrient) *
-                        ui::Transform(invTransform)).getOrientation();
-    }
-
-    if (invTransform & NATIVE_WINDOW_TRANSFORM_ROT_90) {
-        // If the activeCrop has been rotate the ends are rotated but not
-        // the space itself so when transforming ends back we can't rely on
-        // a modification of the axes of rotation. To account for this we
-        // need to reorient the inverse rotation in terms of the current
-        // axes of rotation.
-        bool is_h_flipped = (invTransform & NATIVE_WINDOW_TRANSFORM_FLIP_H) != 0;
-        bool is_v_flipped = (invTransform & NATIVE_WINDOW_TRANSFORM_FLIP_V) != 0;
-        if (is_h_flipped == is_v_flipped) {
-            invTransform ^= NATIVE_WINDOW_TRANSFORM_FLIP_V | NATIVE_WINDOW_TRANSFORM_FLIP_H;
-        }
-        std::swap(winWidth, winHeight);
-    }
-    const Rect winCrop =
-            activeCrop.transform(invTransform, bufferSize.getWidth(), bufferSize.getHeight());
-
-    // below, crop is intersected with winCrop expressed in crop's coordinate space
-    float xScale = crop.getWidth() / float(winWidth);
-    float yScale = crop.getHeight() / float(winHeight);
-
-    float insetL = winCrop.left * xScale;
-    float insetT = winCrop.top * yScale;
-    float insetR = (winWidth - winCrop.right) * xScale;
-    float insetB = (winHeight - winCrop.bottom) * yScale;
-
-    crop.left += insetL;
-    crop.top += insetT;
-    crop.right -= insetR;
-    crop.bottom -= insetB;
-
-    return crop;
-}
-
-void Layer::setGeometry(const sp<const DisplayDevice>& display, uint32_t z) {
-    const auto outputLayer = findOutputLayerForDisplay(display);
-    LOG_FATAL_IF(!outputLayer);
-    LOG_FATAL_IF(!outputLayer->getState().hwc);
-    auto& hwcLayer = (*outputLayer->getState().hwc).hwcLayer;
-
-    if (!hasHwcLayer(display)) {
-        ALOGE("[%s] failed to setGeometry: no HWC layer found (%s)", mName.string(),
-              display->getDebugName().c_str());
-        return;
-    }
-
-    LOG_FATAL_IF(!getCompositionLayer());
-    auto& commonCompositionState = getCompositionLayer()->editState().frontEnd;
-    auto& compositionState = outputLayer->editState();
-
-    // enable this layer
-    compositionState.forceClientComposition = false;
-
-    if (isSecure() && !display->isSecure()) {
-        compositionState.forceClientComposition = true;
-    }
-
-    // this gives us only the "orientation" component of the transform
-    const State& s(getDrawingState());
-    const Rect bufferSize = getBufferSize(s);
+void Layer::latchGeometry(compositionengine::LayerFECompositionState& compositionState) const {
+    const auto& drawingState{getDrawingState()};
+    auto alpha = static_cast<float>(getAlpha());
     auto blendMode = HWC2::BlendMode::None;
-    if (!isOpaque(s) || getAlpha() != 1.0f) {
+    if (!isOpaque(drawingState) || alpha != 1.0f) {
         blendMode =
                 mPremultipliedAlpha ? HWC2::BlendMode::Premultiplied : HWC2::BlendMode::Coverage;
     }
-    auto error = hwcLayer->setBlendMode(blendMode);
-    ALOGE_IF(error != HWC2::Error::None,
-             "[%s] Failed to set blend mode %s:"
-             " %s (%d)",
-             mName.string(), to_string(blendMode).c_str(), to_string(error).c_str(),
-             static_cast<int32_t>(error));
-    commonCompositionState.blendMode = static_cast<Hwc2::IComposerClient::BlendMode>(blendMode);
 
-    // apply the layer's transform, followed by the display's global transform
-    // here we're guaranteed that the layer's transform preserves rects
-    Region activeTransparentRegion(getActiveTransparentRegion(s));
-    ui::Transform t = getTransform();
-    Rect activeCrop = getCrop(s);
-    if (!activeCrop.isEmpty() && bufferSize.isValid()) {
-        activeCrop = t.transform(activeCrop);
-        if (!activeCrop.intersect(display->getViewport(), &activeCrop)) {
-            activeCrop.clear();
-        }
-        activeCrop = t.inverse().transform(activeCrop, true);
-        // This needs to be here as transform.transform(Rect) computes the
-        // transformed rect and then takes the bounding box of the result before
-        // returning. This means
-        // transform.inverse().transform(transform.transform(Rect)) != Rect
-        // in which case we need to make sure the final rect is clipped to the
-        // display bounds.
-        if (!activeCrop.intersect(bufferSize, &activeCrop)) {
-            activeCrop.clear();
-        }
-        // mark regions outside the crop as transparent
-        activeTransparentRegion.orSelf(Rect(0, 0, bufferSize.getWidth(), activeCrop.top));
-        activeTransparentRegion.orSelf(
-                Rect(0, activeCrop.bottom, bufferSize.getWidth(), bufferSize.getHeight()));
-        activeTransparentRegion.orSelf(Rect(0, activeCrop.top, activeCrop.left, activeCrop.bottom));
-        activeTransparentRegion.orSelf(
-                Rect(activeCrop.right, activeCrop.top, bufferSize.getWidth(), activeCrop.bottom));
-    }
-
-    // getBounds returns a FloatRect to provide more accuracy during the
-    // transformation. We then round upon constructing 'frame'.
-    Rect frame{t.transform(getBounds(activeTransparentRegion))};
-    if (!frame.intersect(display->getViewport(), &frame)) {
-        frame.clear();
-    }
-    const ui::Transform& tr = display->getTransform();
-    Rect transformedFrame = tr.transform(frame);
-    error = hwcLayer->setDisplayFrame(transformedFrame);
-    if (error != HWC2::Error::None) {
-        ALOGE("[%s] Failed to set display frame [%d, %d, %d, %d]: %s (%d)", mName.string(),
-              transformedFrame.left, transformedFrame.top, transformedFrame.right,
-              transformedFrame.bottom, to_string(error).c_str(), static_cast<int32_t>(error));
-    } else {
-        compositionState.displayFrame = transformedFrame;
-    }
-
-    FloatRect sourceCrop = computeCrop(display);
-    error = hwcLayer->setSourceCrop(sourceCrop);
-    if (error != HWC2::Error::None) {
-        ALOGE("[%s] Failed to set source crop [%.3f, %.3f, %.3f, %.3f]: "
-              "%s (%d)",
-              mName.string(), sourceCrop.left, sourceCrop.top, sourceCrop.right, sourceCrop.bottom,
-              to_string(error).c_str(), static_cast<int32_t>(error));
-    } else {
-        compositionState.sourceCrop = sourceCrop;
-    }
-
-    float alpha = static_cast<float>(getAlpha());
-    error = hwcLayer->setPlaneAlpha(alpha);
-    ALOGE_IF(error != HWC2::Error::None,
-             "[%s] Failed to set plane alpha %.3f: "
-             "%s (%d)",
-             mName.string(), alpha, to_string(error).c_str(), static_cast<int32_t>(error));
-    commonCompositionState.alpha = alpha;
-
-    error = hwcLayer->setZOrder(z);
-    ALOGE_IF(error != HWC2::Error::None, "[%s] Failed to set Z %u: %s (%d)", mName.string(), z,
-             to_string(error).c_str(), static_cast<int32_t>(error));
-    compositionState.z = z;
-
-    int type = s.metadata.getInt32(METADATA_WINDOW_TYPE, 0);
-    int appId = s.metadata.getInt32(METADATA_OWNER_UID, 0);
+    int type = drawingState.metadata.getInt32(METADATA_WINDOW_TYPE, 0);
+    int appId = drawingState.metadata.getInt32(METADATA_OWNER_UID, 0);
     sp<Layer> parent = mDrawingParent.promote();
     if (parent.get()) {
         auto& parentState = parent->getDrawingState();
@@ -609,60 +408,33 @@
         }
     }
 
-    error = hwcLayer->setInfo(type, appId);
-    ALOGE_IF(error != HWC2::Error::None, "[%s] Failed to set info (%d)", mName.string(),
-             static_cast<int32_t>(error));
+    compositionState.geomLayerTransform = getTransform();
+    compositionState.geomInverseLayerTransform = compositionState.geomLayerTransform.inverse();
+    compositionState.geomBufferSize = getBufferSize(drawingState);
+    compositionState.geomContentCrop = getContentCrop();
+    compositionState.geomCrop = getCrop(drawingState);
+    compositionState.geomBufferTransform = mCurrentTransform;
+    compositionState.geomBufferUsesDisplayInverseTransform = getTransformToDisplayInverse();
+    compositionState.geomActiveTransparentRegion = getActiveTransparentRegion(drawingState);
+    compositionState.geomLayerBounds = mBounds;
+    compositionState.geomUsesSourceCrop = usesSourceCrop();
+    compositionState.isSecure = isSecure();
 
-    commonCompositionState.type = type;
-    commonCompositionState.appId = appId;
+    compositionState.blendMode = static_cast<Hwc2::IComposerClient::BlendMode>(blendMode);
+    compositionState.alpha = alpha;
+    compositionState.type = type;
+    compositionState.appId = appId;
+}
 
-    /*
-     * Transformations are applied in this order:
-     * 1) buffer orientation/flip/mirror
-     * 2) state transformation (window manager)
-     * 3) layer orientation (screen orientation)
-     * (NOTE: the matrices are multiplied in reverse order)
-     */
-
-    const ui::Transform bufferOrientation(mCurrentTransform);
-    ui::Transform transform(tr * t * bufferOrientation);
-
-    if (getTransformToDisplayInverse()) {
-        /*
-         * the code below applies the primary display's inverse transform to the
-         * buffer
-         */
-        uint32_t invTransform = DisplayDevice::getPrimaryDisplayOrientationTransform();
-        // calculate the inverse transform
-        if (invTransform & NATIVE_WINDOW_TRANSFORM_ROT_90) {
-            invTransform ^= NATIVE_WINDOW_TRANSFORM_FLIP_V | NATIVE_WINDOW_TRANSFORM_FLIP_H;
-        }
-
-        /*
-         * Here we cancel out the orientation component of the WM transform.
-         * The scaling and translate components are already included in our bounds
-         * computation so it's enough to just omit it in the composition.
-         * See comment in BufferLayer::prepareClientLayer with ref to b/36727915 for why.
-         */
-        transform = ui::Transform(invTransform) * tr * bufferOrientation;
+void Layer::latchCompositionState(compositionengine::LayerFECompositionState& compositionState,
+                                  bool includeGeometry) const {
+    if (includeGeometry) {
+        latchGeometry(compositionState);
     }
+}
 
-    // this gives us only the "orientation" component of the transform
-    const uint32_t orientation = transform.getOrientation();
-    if (orientation & ui::Transform::ROT_INVALID) {
-        // we can only handle simple transformation
-        compositionState.forceClientComposition = true;
-        (*compositionState.hwc).hwcCompositionType = Hwc2::IComposerClient::Composition::CLIENT;
-    } else {
-        auto transform = static_cast<HWC2::Transform>(orientation);
-        auto error = hwcLayer->setTransform(transform);
-        ALOGE_IF(error != HWC2::Error::None,
-                 "[%s] Failed to set transform %s: "
-                 "%s (%d)",
-                 mName.string(), to_string(transform).c_str(), to_string(error).c_str(),
-                 static_cast<int32_t>(error));
-        compositionState.bufferTransform = static_cast<Hwc2::Transform>(transform);
-    }
+const char* Layer::getDebugName() const {
+    return mName.string();
 }
 
 void Layer::forceClientComposition(const sp<DisplayDevice>& display) {
@@ -2044,8 +1816,24 @@
     mDrawingParent = mCurrentParent;
 }
 
+static wp<Layer> extractLayerFromBinder(const wp<IBinder>& weakBinderHandle) {
+    if (weakBinderHandle == nullptr) {
+        return nullptr;
+    }
+    sp<IBinder> binderHandle = weakBinderHandle.promote();
+    if (binderHandle == nullptr) {
+        return nullptr;
+    }
+    sp<Layer::Handle> handle = static_cast<Layer::Handle*>(binderHandle.get());
+    if (handle == nullptr) {
+        return nullptr;
+    }
+    return handle->owner;
+}
+
 void Layer::setInputInfo(const InputWindowInfo& info) {
     mCurrentState.inputInfo = info;
+    mCurrentState.touchableRegionCrop = extractLayerFromBinder(info.touchableRegionCropHandle);
     mCurrentState.modified = true;
     mCurrentState.inputInfoChanged = true;
     setTransactionFlags(eTransactionNeeded);
@@ -2234,6 +2022,18 @@
     // bounds.
     info.touchableRegion = info.touchableRegion.translate(info.frameLeft, info.frameTop);
     info.visible = canReceiveInput();
+
+    auto cropLayer = mDrawingState.touchableRegionCrop.promote();
+    if (info.replaceTouchableRegionWithCrop) {
+        if (cropLayer == nullptr) {
+            info.touchableRegion = Region(Rect{mScreenBounds});
+        } else {
+            info.touchableRegion = Region(Rect{cropLayer->mScreenBounds});
+        }
+    } else if (cropLayer != nullptr) {
+        info.touchableRegion = info.touchableRegion.intersect(Rect{cropLayer->mScreenBounds});
+    }
+
     return info;
 }
 
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index f333d0a..89063da 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -70,6 +70,7 @@
 namespace compositionengine {
 class Layer;
 class OutputLayer;
+struct LayerFECompositionState;
 }
 
 namespace impl {
@@ -183,6 +184,7 @@
 
         bool inputInfoChanged;
         InputWindowInfo inputInfo;
+        wp<Layer> touchableRegionCrop;
 
         // dataspace is only used by BufferStateLayer and ColorLayer
         ui::Dataspace dataspace;
@@ -419,6 +421,11 @@
      */
     virtual bool isFixedSize() const { return true; }
 
+    /*
+     * usesSourceCrop - true if content should use a source crop
+     */
+    virtual bool usesSourceCrop() const { return false; }
+
     // Most layers aren't created from the main thread, and therefore need to
     // grab the SF state lock to access HWC, but ContainerLayer does, so we need
     // to avoid grabbing the lock again to avoid deadlock
@@ -455,13 +462,19 @@
     /*
      * compositionengine::LayerFE overrides
      */
+    void latchCompositionState(compositionengine::LayerFECompositionState&,
+                               bool includeGeometry) const override;
     void onLayerDisplayed(const sp<Fence>& releaseFence) override;
+    const char* getDebugName() const override;
 
+protected:
+    void latchGeometry(compositionengine::LayerFECompositionState& outState) const;
+
+public:
     virtual void setDefaultBufferSize(uint32_t /*w*/, uint32_t /*h*/) {}
 
     virtual bool isHdrY410() const { return false; }
 
-    void setGeometry(const sp<const DisplayDevice>& display, uint32_t z);
     void forceClientComposition(const sp<DisplayDevice>& display);
     bool getForceClientComposition(const sp<DisplayDevice>& display);
     virtual void setPerFrameData(const sp<const DisplayDevice>& display,
@@ -708,12 +721,6 @@
 
     uint32_t getEffectiveUsage(uint32_t usage) const;
 
-    virtual FloatRect computeCrop(const sp<const DisplayDevice>& display) const;
-    // Compute the initial crop as specified by parent layers and the
-    // SurfaceControl for this layer. Does not include buffer crop from the
-    // IGraphicBufferProducer client, as that should not affect child clipping.
-    // Returns in screen space.
-    Rect computeInitialCrop(const sp<const DisplayDevice>& display) const;
     /**
      * Setup rounded corners coordinates of this layer, taking into account the layer bounds and
      * crop coordinates, transforming them into layer space.
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 37b9466..ca94e15 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -1758,24 +1758,30 @@
         mGeometryInvalid = false;
         for (const auto& [token, displayDevice] : mDisplays) {
             auto display = displayDevice->getCompositionDisplay();
-            const auto displayId = display->getId();
-            if (!displayId) {
-                continue;
-            }
 
-            const Vector<sp<Layer>>& currentLayers = displayDevice->getVisibleLayersSortedByZ();
-            for (size_t i = 0; i < currentLayers.size(); i++) {
-                const auto& layer = currentLayers[i];
+            uint32_t zOrder = 0;
 
-                if (!layer->hasHwcLayer(displayDevice)) {
-                    layer->forceClientComposition(displayDevice);
-                    continue;
+            for (auto& layer : display->getOutputLayersOrderedByZ()) {
+                auto& compositionState = layer->editState();
+                compositionState.forceClientComposition = false;
+                if (!compositionState.hwc || mDebugDisableHWC || mDebugRegion) {
+                    compositionState.forceClientComposition = true;
                 }
 
-                layer->setGeometry(displayDevice, i);
-                if (mDebugDisableHWC || mDebugRegion) {
-                    layer->forceClientComposition(displayDevice);
-                }
+                // The output Z order is set here based on a simple counter.
+                compositionState.z = zOrder++;
+
+                // Update the display independent composition state. This goes
+                // to the general composition layer state structure.
+                // TODO: Do this once per compositionengine::CompositionLayer.
+                layer->getLayerFE().latchCompositionState(layer->getLayer().editState().frontEnd,
+                                                          true);
+
+                // Recalculate the geometry state of the output layer.
+                layer->updateCompositionState(true);
+
+                // Write the updated geometry state to the HWC
+                layer->writeStateToHWC(true);
             }
         }
     }
@@ -3282,17 +3288,7 @@
         clientCompositionDisplay.physicalDisplay = displayState.scissor;
         clientCompositionDisplay.clip = displayState.scissor;
         const ui::Transform& displayTransform = displayState.transform;
-        mat4 m;
-        m[0][0] = displayTransform[0][0];
-        m[0][1] = displayTransform[0][1];
-        m[0][3] = displayTransform[0][2];
-        m[1][0] = displayTransform[1][0];
-        m[1][1] = displayTransform[1][1];
-        m[1][3] = displayTransform[1][2];
-        m[3][0] = displayTransform[2][0];
-        m[3][1] = displayTransform[2][1];
-        m[3][3] = displayTransform[2][2];
-        clientCompositionDisplay.globalTransform = m;
+        clientCompositionDisplay.globalTransform = displayTransform.asMatrix4();
 
         const auto* profile = display->getDisplayColorProfile();
         Dataspace outputDataspace = Dataspace::UNKNOWN;
@@ -4392,6 +4388,14 @@
     return NO_ERROR;
 }
 
+status_t SurfaceFlinger::dumpCritical(int fd, const DumpArgs&, bool asProto) {
+    if (asProto && mTracing.isEnabled()) {
+        mTracing.writeToFileAsync();
+    }
+
+    return doDump(fd, DumpArgs(), asProto);
+}
+
 void SurfaceFlinger::listLayersLocked(std::string& result) const {
     mCurrentState.traverseInZOrder(
             [&](Layer* layer) { StringAppendF(&result, "%s\n", layer->getName().string()); });
@@ -4714,6 +4718,14 @@
         result.append("\n");
     }
 
+    {
+        StringAppendF(&result, "Composition layers\n");
+        mDrawingState.traverseInZOrder([&](Layer* layer) {
+            auto compositionLayer = layer->getCompositionLayer();
+            if (compositionLayer) compositionLayer->dump(result);
+        });
+    }
+
     /*
      * Dump Display state
      */
@@ -5569,18 +5581,7 @@
     // buffer bounds.
     clientCompositionDisplay.physicalDisplay = Rect(reqWidth, reqHeight);
     ui::Transform transform = renderArea.getTransform();
-    mat4 m;
-    m[0][0] = transform[0][0];
-    m[0][1] = transform[0][1];
-    m[0][3] = transform[0][2];
-    m[1][0] = transform[1][0];
-    m[1][1] = transform[1][1];
-    m[1][3] = transform[1][2];
-    m[3][0] = transform[2][0];
-    m[3][1] = transform[2][1];
-    m[3][3] = transform[2][2];
-
-    clientCompositionDisplay.globalTransform = m;
+    clientCompositionDisplay.globalTransform = transform.asMatrix4();
     mat4 rotMatrix;
     // Displacement for repositioning the clipping rectangle after rotating it
     // with the rotation hint.
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 103672c..0d39cb5 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -905,9 +905,7 @@
 
     status_t doDump(int fd, const DumpArgs& args, bool asProto);
 
-    status_t dumpCritical(int fd, const DumpArgs&, bool asProto) override {
-        return doDump(fd, DumpArgs(), asProto);
-    }
+    status_t dumpCritical(int fd, const DumpArgs&, bool asProto);
 
     status_t dumpAll(int fd, const DumpArgs& args, bool asProto) override {
         return doDump(fd, args, asProto);