Add support for preTransform in Vulkan swapchain.

Test: manual building and running of apps.
Bug: 110985606
Bug: 122662274
Change-Id: Id53aa1ce028b448b1f12558eec78a60e09512166
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index e6e6b0e..cfbb995 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -100,7 +100,8 @@
             mSurfaceColorSpace, &props));
 
     SkiaPipeline::updateLighting(lightGeometry, lightInfo);
-    renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface);
+    renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface,
+            SkMatrix::I());
     layerUpdateQueue->clear();
 
     // Draw visual debugging features
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index d7faaf7..df82243 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -312,7 +312,8 @@
 
 void SkiaPipeline::renderFrame(const LayerUpdateQueue& layers, const SkRect& clip,
                                const std::vector<sp<RenderNode>>& nodes, bool opaque,
-                               const Rect& contentDrawBounds, sk_sp<SkSurface> surface) {
+                               const Rect& contentDrawBounds, sk_sp<SkSurface> surface,
+                               const SkMatrix& preTransform) {
     renderVectorDrawableCache();
 
     // draw all layers up front
@@ -323,12 +324,12 @@
     std::unique_ptr<SkPictureRecorder> recorder;
     SkCanvas* canvas = tryCapture(surface.get());
 
-    renderFrameImpl(layers, clip, nodes, opaque, contentDrawBounds, canvas);
+    renderFrameImpl(layers, clip, nodes, opaque, contentDrawBounds, canvas, preTransform);
 
     endCapture(surface.get());
 
     if (CC_UNLIKELY(Properties::debugOverdraw)) {
-        renderOverdraw(layers, clip, nodes, contentDrawBounds, surface);
+        renderOverdraw(layers, clip, nodes, contentDrawBounds, surface, preTransform);
     }
 
     ATRACE_NAME("flush commands");
@@ -344,9 +345,11 @@
 
 void SkiaPipeline::renderFrameImpl(const LayerUpdateQueue& layers, const SkRect& clip,
                                    const std::vector<sp<RenderNode>>& nodes, bool opaque,
-                                   const Rect& contentDrawBounds, SkCanvas* canvas) {
+                                   const Rect& contentDrawBounds, SkCanvas* canvas,
+                                   const SkMatrix& preTransform) {
     SkAutoCanvasRestore saver(canvas, true);
-    canvas->androidFramework_setDeviceClipRestriction(clip.roundOut());
+    canvas->androidFramework_setDeviceClipRestriction(preTransform.mapRect(clip).roundOut());
+    canvas->concat(preTransform);
 
     // STOPSHIP: Revert, temporary workaround to clear always F16 frame buffer for b/74976293
     if (!opaque || getSurfaceColorType() == kRGBA_F16_SkColorType) {
@@ -486,7 +489,8 @@
 
 void SkiaPipeline::renderOverdraw(const LayerUpdateQueue& layers, const SkRect& clip,
                                   const std::vector<sp<RenderNode>>& nodes,
-                                  const Rect& contentDrawBounds, sk_sp<SkSurface> surface) {
+                                  const Rect& contentDrawBounds, sk_sp<SkSurface> surface,
+                                  const SkMatrix& preTransform) {
     // Set up the overdraw canvas.
     SkImageInfo offscreenInfo = SkImageInfo::MakeA8(surface->width(), surface->height());
     sk_sp<SkSurface> offscreen = surface->makeSurface(offscreenInfo);
@@ -496,7 +500,7 @@
     // each time a pixel would have been drawn.
     // Pass true for opaque so we skip the clear - the overdrawCanvas is already zero
     // initialized.
-    renderFrameImpl(layers, clip, nodes, true, contentDrawBounds, &overdrawCanvas);
+    renderFrameImpl(layers, clip, nodes, true, contentDrawBounds, &overdrawCanvas, preTransform);
     sk_sp<SkImage> counts = offscreen->makeImageSnapshot();
 
     // Draw overdraw colors to the canvas.  The color filter will convert counts to colors.
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h
index 94a699b..cf6f5b2 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.h
@@ -53,7 +53,8 @@
 
     void renderFrame(const LayerUpdateQueue& layers, const SkRect& clip,
                      const std::vector<sp<RenderNode>>& nodes, bool opaque,
-                     const Rect& contentDrawBounds, sk_sp<SkSurface> surface);
+                     const Rect& contentDrawBounds, sk_sp<SkSurface> surface,
+                     const SkMatrix& preTransform);
 
     std::vector<VectorDrawableRoot*>* getVectorDrawables() { return &mVectorDrawables; }
 
@@ -116,7 +117,8 @@
 private:
     void renderFrameImpl(const LayerUpdateQueue& layers, const SkRect& clip,
                          const std::vector<sp<RenderNode>>& nodes, bool opaque,
-                         const Rect& contentDrawBounds, SkCanvas* canvas);
+                         const Rect& contentDrawBounds, SkCanvas* canvas,
+                         const SkMatrix& preTransform);
 
     /**
      *  Debugging feature.  Draws a semi-transparent overlay on each pixel, indicating
@@ -124,7 +126,7 @@
      */
     void renderOverdraw(const LayerUpdateQueue& layers, const SkRect& clip,
                         const std::vector<sp<RenderNode>>& nodes, const Rect& contentDrawBounds,
-                        sk_sp<SkSurface>);
+                        sk_sp<SkSurface> surface, const SkMatrix& preTransform);
 
     /**
      *  Render mVectorDrawables into offscreen buffers.
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
index b9aae98..53495a7 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
@@ -58,7 +58,8 @@
         return Frame(-1, -1, 0);
     }
 
-    Frame frame(backBuffer->width(), backBuffer->height(), mVkManager.getAge(mVkSurface));
+    Frame frame(mVkSurface->windowWidth(), mVkSurface->windowHeight(),
+                mVkManager.getAge(mVkSurface));
     return frame;
 }
 
@@ -73,7 +74,8 @@
         return false;
     }
     SkiaPipeline::updateLighting(lightGeometry, lightInfo);
-    renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, backBuffer);
+    renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds,
+            backBuffer, mVkSurface->preTransform());
     ShaderCache::get().onVkFrameFlushed(mRenderThread.getGrContext());
     layerUpdateQueue->clear();
 
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index 6c540f6..5c6cb9a 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -480,6 +480,32 @@
     return backbuffer;
 }
 
+static SkMatrix getPreTransformMatrix(int width, int height,
+                                      VkSurfaceTransformFlagBitsKHR transform) {
+    switch (transform) {
+        case VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR:
+            return SkMatrix::I();
+        case VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR:
+            return SkMatrix::MakeAll(0, -1, height, 1, 0, 0, 0, 0, 1);
+        case VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR:
+            return SkMatrix::MakeAll(-1, 0, width, 0, -1, height, 0, 0, 1);
+        case VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR:
+            return SkMatrix::MakeAll(0, 1, 0, -1, 0, width, 0, 0, 1);
+        case VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_BIT_KHR:
+            return SkMatrix::MakeAll(-1, 0, width, 0, 1, 0, 0, 0, 1);
+        case VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_ROTATE_90_BIT_KHR:
+            return SkMatrix::MakeAll(0, -1, height, -1, 0, width, 0, 0, 1);
+        case VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_ROTATE_180_BIT_KHR:
+            return SkMatrix::MakeAll(1, 0, 0, 0, -1, height, 0, 0, 1);
+        case VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_ROTATE_270_BIT_KHR:
+            return SkMatrix::MakeAll(0, 1, 0, 1, 0, 0, 0, 0, 1);
+        default:
+            LOG_ALWAYS_FATAL("Unsupported pre transform of swapchain.");
+    }
+    return SkMatrix::I();
+}
+
+
 SkSurface* VulkanManager::getBackbufferSurface(VulkanSurface** surfaceOut) {
     // Recreate VulkanSurface, if ANativeWindow has been resized.
     VulkanSurface* surface = *surfaceOut;
@@ -516,7 +542,7 @@
         // maybe use attach somehow? but need a Window
         return nullptr;
     }
-    if (VK_ERROR_OUT_OF_DATE_KHR == res) {
+    if (VK_ERROR_OUT_OF_DATE_KHR == res || VK_SUBOPTIMAL_KHR == res) {
         // tear swapchain down and try again
         if (!createSwapchain(surface)) {
             return nullptr;
@@ -595,6 +621,10 @@
     }
     backendRT.setVkImageLayout(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
 
+    surface->mPreTransform = getPreTransformMatrix(surface->windowWidth(),
+                                                   surface->windowHeight(),
+                                                   surface->mTransform);
+
     surface->mBackbuffer = std::move(skSurface);
     return surface->mBackbuffer.get();
 }
@@ -749,6 +779,17 @@
         return false;
     }
 
+    if (!SkToBool(caps.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR)) {
+        return false;
+    }
+    VkSurfaceTransformFlagBitsKHR transform;
+    if (SkToBool(caps.supportedTransforms & caps.currentTransform) &&
+        !SkToBool(caps.currentTransform & VK_SURFACE_TRANSFORM_INHERIT_BIT_KHR)) {
+        transform = caps.currentTransform;
+    } else {
+        transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
+    }
+
     VkExtent2D extent = caps.currentExtent;
     // clamp width; to handle currentExtent of -1 and  protect us from broken hints
     if (extent.width < caps.minImageExtent.width) {
@@ -760,6 +801,16 @@
         extent.height = caps.minImageExtent.height;
     }
     SkASSERT(extent.height <= caps.maxImageExtent.height);
+
+    VkExtent2D swapExtent = extent;
+    if (transform == VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR ||
+        transform == VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR ||
+        transform == VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_ROTATE_90_BIT_KHR ||
+        transform == VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_ROTATE_270_BIT_KHR) {
+        swapExtent.width = extent.height;
+        swapExtent.height = extent.width;
+    }
+
     surface->mWindowWidth = extent.width;
     surface->mWindowHeight = extent.height;
 
@@ -775,7 +826,7 @@
                                    VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
                                    VK_IMAGE_USAGE_TRANSFER_DST_BIT;
     SkASSERT((caps.supportedUsageFlags & usageFlags) == usageFlags);
-    SkASSERT(caps.supportedTransforms & caps.currentTransform);
+
     SkASSERT(caps.supportedCompositeAlpha &
              (VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR | VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR));
     VkCompositeAlphaFlagBitsKHR composite_alpha =
@@ -822,7 +873,7 @@
     swapchainCreateInfo.minImageCount = imageCount;
     swapchainCreateInfo.imageFormat = surfaceFormat;
     swapchainCreateInfo.imageColorSpace = colorSpace;
-    swapchainCreateInfo.imageExtent = extent;
+    swapchainCreateInfo.imageExtent = swapExtent;
     swapchainCreateInfo.imageArrayLayers = 1;
     swapchainCreateInfo.imageUsage = usageFlags;
 
@@ -837,7 +888,7 @@
         swapchainCreateInfo.pQueueFamilyIndices = nullptr;
     }
 
-    swapchainCreateInfo.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
+    swapchainCreateInfo.preTransform = transform;
     swapchainCreateInfo.compositeAlpha = composite_alpha;
     swapchainCreateInfo.presentMode = mode;
     swapchainCreateInfo.clipped = true;
@@ -848,6 +899,8 @@
         return false;
     }
 
+    surface->mTransform = transform;
+
     // destroy the old swapchain
     if (swapchainCreateInfo.oldSwapchain != VK_NULL_HANDLE) {
         mDeviceWaitIdle(mDevice);
@@ -857,7 +910,7 @@
         mDestroySwapchainKHR(mDevice, swapchainCreateInfo.oldSwapchain, nullptr);
     }
 
-    createBuffers(surface, surfaceFormat, extent);
+    createBuffers(surface, surfaceFormat, swapExtent);
 
     // The window content is not updated (frozen) until a buffer of the window size is received.
     // This prevents temporary stretching of the window after it is resized, but before the first
diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h
index 105ee09..b06eb82 100644
--- a/libs/hwui/renderthread/VulkanManager.h
+++ b/libs/hwui/renderthread/VulkanManager.h
@@ -45,6 +45,14 @@
 
     sk_sp<SkSurface> getBackBufferSurface() { return mBackbuffer; }
 
+    // The width and height are are the logical width and height for when submitting draws to the
+    // surface. In reality if the window is rotated the underlying VkImage may have the width and
+    // height swapped.
+    int windowWidth() const { return mWindowWidth; }
+    int windowHeight() const { return mWindowHeight; }
+
+    SkMatrix& preTransform() { return mPreTransform; }
+
 private:
     friend class VulkanManager;
     struct BackbufferInfo {
@@ -84,6 +92,8 @@
     sk_sp<SkColorSpace> mColorSpace;
     SkColorSpace::Gamut mColorGamut;
     SkColorType mColorType;
+    VkSurfaceTransformFlagBitsKHR mTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
+    SkMatrix mPreTransform;
 };
 
 // This class contains the shared global Vulkan objects, such as VkInstance, VkDevice and VkQueue,
diff --git a/libs/hwui/tests/unit/SkiaPipelineTests.cpp b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
index 3c06dab..e86cf42 100644
--- a/libs/hwui/tests/unit/SkiaPipelineTests.cpp
+++ b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
@@ -51,7 +51,8 @@
     auto surface = SkSurface::MakeRasterN32Premul(1, 1);
     surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
-    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface);
+    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface,
+            SkMatrix::I());
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorRED);
 }
 
@@ -83,7 +84,8 @@
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
 
     // drawFrame will crash if "SkiaPipeline::onPrepareTree" did not clean invalid VD pointer
-    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface);
+    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface,
+            SkMatrix::I());
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorRED);
 }
 
@@ -104,10 +106,12 @@
     auto surface = SkSurface::MakeRasterN32Premul(2, 2);
     surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
-    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, true, contentDrawBounds, surface);
+    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, true, contentDrawBounds, surface,
+            SkMatrix::I());
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
     ASSERT_EQ(TestUtils::getColor(surface, 0, 1), SK_ColorGREEN);
-    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, false, contentDrawBounds, surface);
+    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, false, contentDrawBounds, surface,
+            SkMatrix::I());
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), (unsigned int)SK_ColorTRANSPARENT);
     ASSERT_EQ(TestUtils::getColor(surface, 0, 1), SK_ColorGREEN);
 }
@@ -126,7 +130,8 @@
     auto surface = SkSurface::MakeRasterN32Premul(2, 2);
     surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
-    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, true, contentDrawBounds, surface);
+    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, true, contentDrawBounds, surface,
+            SkMatrix::I());
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
     ASSERT_EQ(TestUtils::getColor(surface, 1, 0), SK_ColorBLUE);
     ASSERT_EQ(TestUtils::getColor(surface, 0, 1), SK_ColorRED);
@@ -198,32 +203,38 @@
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
 
     // Single draw, should be white.
-    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface);
+    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface,
+            SkMatrix::I());
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorWHITE);
 
     // 1 Overdraw, should be blue blended onto white.
     renderNodes.push_back(whiteNode);
-    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface);
+    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface,
+            SkMatrix::I());
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), (unsigned)0xffd0d0ff);
 
     // 2 Overdraw, should be green blended onto white
     renderNodes.push_back(whiteNode);
-    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface);
+    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface,
+            SkMatrix::I());
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), (unsigned)0xffd0ffd0);
 
     // 3 Overdraw, should be pink blended onto white.
     renderNodes.push_back(whiteNode);
-    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface);
+    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface,
+            SkMatrix::I());
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), (unsigned)0xffffc0c0);
 
     // 4 Overdraw, should be red blended onto white.
     renderNodes.push_back(whiteNode);
-    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface);
+    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface,
+            SkMatrix::I());
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), (unsigned)0xffff8080);
 
     // 5 Overdraw, should be red blended onto white.
     renderNodes.push_back(whiteNode);
-    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface);
+    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface,
+            SkMatrix::I());
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), (unsigned)0xffff8080);
 }
 
@@ -308,7 +319,8 @@
     SkRect dirty = SkRect::MakeWH(800, 600);
     auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
     sk_sp<DeferLayer<DeferTestCanvas>> surface(new DeferLayer<DeferTestCanvas>());
-    pipeline->renderFrame(layerUpdateQueue, dirty, nodes, true, contentDrawBounds, surface);
+    pipeline->renderFrame(layerUpdateQueue, dirty, nodes, true, contentDrawBounds, surface,
+            SkMatrix::I());
     EXPECT_EQ(4, surface->canvas()->mDrawCounter);
 }
 
@@ -339,7 +351,43 @@
     auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
     sk_sp<DeferLayer<ClippedTestCanvas>> surface(new DeferLayer<ClippedTestCanvas>());
     pipeline->renderFrame(layerUpdateQueue, dirty, nodes, true,
-                          SkRect::MakeWH(CANVAS_WIDTH, CANVAS_HEIGHT), surface);
+                          SkRect::MakeWH(CANVAS_WIDTH, CANVAS_HEIGHT), surface, SkMatrix::I());
+    EXPECT_EQ(1, surface->canvas()->mDrawCounter);
+}
+
+// Test renderFrame with a dirty clip and a pre-transform matrix.
+RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, clipped_rotated) {
+    static const int CANVAS_WIDTH = 200;
+    static const int CANVAS_HEIGHT = 100;
+    static const SkMatrix rotateMatrix = SkMatrix::MakeAll(0, -1, CANVAS_HEIGHT, 1, 0, 0, 0, 0, 1);
+    static const SkRect dirty = SkRect::MakeLTRB(10, 20, 20, 40);
+    class ClippedTestCanvas : public SkCanvas {
+    public:
+        ClippedTestCanvas() : SkCanvas(CANVAS_WIDTH, CANVAS_HEIGHT) {}
+        void onDrawImage(const SkImage*, SkScalar dx, SkScalar dy, const SkPaint*) override {
+            EXPECT_EQ(0, mDrawCounter++);
+            // Expect clip to be rotated.
+            EXPECT_EQ(SkRect::MakeLTRB(CANVAS_HEIGHT - dirty.fTop - dirty.height(), dirty.fLeft,
+                    CANVAS_HEIGHT - dirty.fTop, dirty.fLeft + dirty.width()),
+                    TestUtils::getClipBounds(this));
+            EXPECT_EQ(rotateMatrix, getTotalMatrix());
+        }
+        int mDrawCounter = 0;
+    };
+
+    std::vector<sp<RenderNode>> nodes;
+    nodes.push_back(TestUtils::createSkiaNode(
+            0, 0, CANVAS_WIDTH, CANVAS_HEIGHT,
+            [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+                sk_sp<Bitmap> bitmap(TestUtils::createBitmap(CANVAS_WIDTH, CANVAS_HEIGHT));
+                canvas.drawBitmap(*bitmap, 0, 0, nullptr);
+            }));
+
+    LayerUpdateQueue layerUpdateQueue;
+    auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
+    sk_sp<DeferLayer<ClippedTestCanvas>> surface(new DeferLayer<ClippedTestCanvas>());
+    pipeline->renderFrame(layerUpdateQueue, dirty, nodes, true,
+                          SkRect::MakeWH(CANVAS_WIDTH, CANVAS_HEIGHT), surface, rotateMatrix);
     EXPECT_EQ(1, surface->canvas()->mDrawCounter);
 }
 
@@ -369,7 +417,7 @@
     auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
     sk_sp<DeferLayer<ClipReplaceTestCanvas>> surface(new DeferLayer<ClipReplaceTestCanvas>());
     pipeline->renderFrame(layerUpdateQueue, dirty, nodes, true,
-                          SkRect::MakeWH(CANVAS_WIDTH, CANVAS_HEIGHT), surface);
+                          SkRect::MakeWH(CANVAS_WIDTH, CANVAS_HEIGHT), surface, SkMatrix::I());
     EXPECT_EQ(1, surface->canvas()->mDrawCounter);
 }