Apply clip at reorder barriers to shadows

Fixes: 28004930

Change-Id: I208b78430c770a3135afd68c53cf8ff3fba50c52
diff --git a/libs/hwui/DisplayList.h b/libs/hwui/DisplayList.h
index e209e2a..aba5d4b 100644
--- a/libs/hwui/DisplayList.h
+++ b/libs/hwui/DisplayList.h
@@ -137,6 +137,9 @@
 
         // whether children with non-zero Z in the chunk should be reordered
         bool reorderChildren;
+#if HWUI_NEW_OPS
+        const ClipBase* reorderClip;
+#endif
     };
 
     DisplayList();
diff --git a/libs/hwui/FrameBuilder.cpp b/libs/hwui/FrameBuilder.cpp
index f12e523..6fc74a5 100644
--- a/libs/hwui/FrameBuilder.cpp
+++ b/libs/hwui/FrameBuilder.cpp
@@ -269,7 +269,8 @@
 }
 
 template <typename V>
-void FrameBuilder::defer3dChildren(ChildrenSelectMode mode, const V& zTranslatedNodes) {
+void FrameBuilder::defer3dChildren(const ClipBase* reorderClip, ChildrenSelectMode mode,
+        const V& zTranslatedNodes) {
     const int size = zTranslatedNodes.size();
     if (size == 0
             || (mode == ChildrenSelectMode::Negative&& zTranslatedNodes[0].key > 0.0f)
@@ -305,7 +306,7 @@
             // attempt to render the shadow if the caster about to be drawn is its caster,
             // OR if its caster's Z value is similar to the previous potential caster
             if (shadowIndex == drawIndex || casterZ - lastCasterZ < 0.1f) {
-                deferShadow(*casterNodeOp);
+                deferShadow(reorderClip, *casterNodeOp);
 
                 lastCasterZ = casterZ; // must do this even if current caster not casting a shadow
                 shadowIndex++;
@@ -319,7 +320,7 @@
     }
 }
 
-void FrameBuilder::deferShadow(const RenderNodeOp& casterNodeOp) {
+void FrameBuilder::deferShadow(const ClipBase* reorderClip, const RenderNodeOp& casterNodeOp) {
     auto& node = *casterNodeOp.renderNode;
     auto& properties = node.properties();
 
@@ -365,6 +366,10 @@
         casterPath = frameAllocatedPath;
     }
 
+    // apply reorder clip to shadow, so it respects clip at beginning of reorderable chunk
+    int restoreTo = mCanvasState.save(SaveFlags::MatrixClip);
+    mCanvasState.writableSnapshot()->applyClip(reorderClip,
+            *mCanvasState.currentSnapshot()->transform);
     if (CC_LIKELY(!mCanvasState.getRenderTargetClipBounds().isEmpty())) {
         Matrix4 shadowMatrixXY(casterNodeOp.localMatrix);
         Matrix4 shadowMatrixZ(casterNodeOp.localMatrix);
@@ -386,6 +391,7 @@
             currentLayer().deferUnmergeableOp(mAllocator, bakedOpState, OpBatchType::Shadow);
         }
     }
+    mCanvasState.restoreToCount(restoreTo);
 }
 
 void FrameBuilder::deferProjectedChildren(const RenderNode& renderNode) {
@@ -438,11 +444,11 @@
 
     // can't be null, since DL=null node rejection happens before deferNodePropsAndOps
     const DisplayList& displayList = *(renderNode.getDisplayList());
-    for (const DisplayList::Chunk& chunk : displayList.getChunks()) {
+    for (auto& chunk : displayList.getChunks()) {
         FatVector<ZRenderNodeOpPair, 16> zTranslatedNodes;
         buildZSortedChildList(&zTranslatedNodes, displayList, chunk);
 
-        defer3dChildren(ChildrenSelectMode::Negative, zTranslatedNodes);
+        defer3dChildren(chunk.reorderClip, ChildrenSelectMode::Negative, zTranslatedNodes);
         for (size_t opIndex = chunk.beginOpIndex; opIndex < chunk.endOpIndex; opIndex++) {
             const RecordedOp* op = displayList.getOps()[opIndex];
             receivers[op->opId](*this, *op);
@@ -453,7 +459,7 @@
                 deferProjectedChildren(renderNode);
             }
         }
-        defer3dChildren(ChildrenSelectMode::Positive, zTranslatedNodes);
+        defer3dChildren(chunk.reorderClip, ChildrenSelectMode::Positive, zTranslatedNodes);
     }
 }
 
diff --git a/libs/hwui/FrameBuilder.h b/libs/hwui/FrameBuilder.h
index 039ab6b..a6fd761 100644
--- a/libs/hwui/FrameBuilder.h
+++ b/libs/hwui/FrameBuilder.h
@@ -193,9 +193,10 @@
     void deferNodePropsAndOps(RenderNode& node);
 
     template <typename V>
-    void defer3dChildren(ChildrenSelectMode mode, const V& zTranslatedNodes);
+    void defer3dChildren(const ClipBase* reorderClip, ChildrenSelectMode mode,
+            const V& zTranslatedNodes);
 
-    void deferShadow(const RenderNodeOp& casterOp);
+    void deferShadow(const ClipBase* reorderClip, const RenderNodeOp& casterOp);
 
     void deferProjectedChildren(const RenderNode& renderNode);
 
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index b78497d..ab733f1 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -57,6 +57,16 @@
     return displayList;
 }
 
+void RecordingCanvas::insertReorderBarrier(bool enableReorder) {
+    if (enableReorder) {
+        mDeferredBarrierType = DeferredBarrierType::OutOfOrder;
+        mDeferredBarrierClip = getRecordedClip();
+    } else {
+        mDeferredBarrierType = DeferredBarrierType::InOrder;
+        mDeferredBarrierClip = nullptr;
+    }
+}
+
 SkCanvas* RecordingCanvas::asSkCanvas() {
     LOG_ALWAYS_FATAL_IF(!mDisplayList,
             "attempting to get an SkCanvas when we are not recording!");
@@ -610,6 +620,7 @@
         newChunk.beginOpIndex = insertIndex;
         newChunk.endOpIndex = insertIndex + 1;
         newChunk.reorderChildren = (mDeferredBarrierType == DeferredBarrierType::OutOfOrder);
+        newChunk.reorderClip = mDeferredBarrierClip;
 
         int nextChildIndex = mDisplayList->children.size();
         newChunk.beginChildIndex = newChunk.endChildIndex = nextChildIndex;
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index acb88e2..219296c 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -55,10 +55,7 @@
 // ----------------------------------------------------------------------------
 // MISC HWUI OPERATIONS - TODO: CATEGORIZE
 // ----------------------------------------------------------------------------
-    virtual void insertReorderBarrier(bool enableReorder) override {
-        mDeferredBarrierType = enableReorder
-                ? DeferredBarrierType::OutOfOrder : DeferredBarrierType::InOrder;
-    }
+    virtual void insertReorderBarrier(bool enableReorder) override;
 
     virtual void drawLayer(DeferredLayerUpdater* layerHandle) override;
     virtual void drawRenderNode(RenderNode* renderNode) override;
@@ -312,6 +309,7 @@
     std::unique_ptr<SkiaCanvasProxy> mSkiaCanvasProxy;
     ResourceCache& mResourceCache;
     DeferredBarrierType mDeferredBarrierType = DeferredBarrierType::None;
+    const ClipBase* mDeferredBarrierClip = nullptr;
     DisplayList* mDisplayList = nullptr;
     bool mHighContrastText = false;
     SkAutoTUnref<SkDrawFilter> mDrawFilter;
diff --git a/libs/hwui/tests/unit/FrameBuilderTests.cpp b/libs/hwui/tests/unit/FrameBuilderTests.cpp
index 9877439..209a104 100644
--- a/libs/hwui/tests/unit/FrameBuilderTests.cpp
+++ b/libs/hwui/tests/unit/FrameBuilderTests.cpp
@@ -1722,6 +1722,35 @@
     EXPECT_EQ(4, renderer.getIndex());
 }
 
+RENDERTHREAD_TEST(FrameBuilder, shadowClipping) {
+    class ShadowClippingTestRenderer : public TestRendererBase {
+    public:
+        void onShadowOp(const ShadowOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(0, mIndex++);
+            EXPECT_EQ(Rect(25, 25, 75, 75), state.computedState.clipState->rect)
+                    << "Shadow must respect pre-barrier canvas clip value.";
+        }
+        void onRectOp(const RectOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(1, mIndex++);
+        }
+    };
+    auto parent = TestUtils::createNode(0, 0, 100, 100,
+            [](RenderProperties& props, RecordingCanvas& canvas) {
+        // Apply a clip before the reorder barrier/shadow casting child is drawn.
+        // This clip must be applied to the shadow cast by the child.
+        canvas.clipRect(25, 25, 75, 75, SkRegion::kIntersect_Op);
+        canvas.insertReorderBarrier(true);
+        canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get());
+    });
+
+    FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100,
+            TestUtils::createSyncedNodeList(parent),
+            (FrameBuilder::LightGeometry) {{ 100, 100, 100 }, 50}, Caches::getInstance());
+    ShadowClippingTestRenderer renderer;
+    frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(2, renderer.getIndex());
+}
+
 static void testProperty(std::function<void(RenderProperties&)> propSetupCallback,
         std::function<void(const RectOp&, const BakedOpState&)> opValidateCallback) {
     class PropertyTestRenderer : public TestRendererBase {
diff --git a/libs/hwui/tests/unit/RecordingCanvasTests.cpp b/libs/hwui/tests/unit/RecordingCanvasTests.cpp
index c49ff71..18171de 100644
--- a/libs/hwui/tests/unit/RecordingCanvasTests.cpp
+++ b/libs/hwui/tests/unit/RecordingCanvasTests.cpp
@@ -603,6 +603,36 @@
     EXPECT_TRUE(chunks[1].reorderChildren);
 }
 
+TEST(RecordingCanvas, insertReorderBarrier_clip) {
+    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+        // first chunk: no recorded clip
+        canvas.insertReorderBarrier(true);
+        canvas.drawRect(0, 0, 400, 400, SkPaint());
+
+        // second chunk: no recorded clip, since inorder region
+        canvas.clipRect(0, 0, 200, 200, SkRegion::kIntersect_Op);
+        canvas.insertReorderBarrier(false);
+        canvas.drawRect(0, 0, 400, 400, SkPaint());
+
+        // third chunk: recorded clip
+        canvas.insertReorderBarrier(true);
+        canvas.drawRect(0, 0, 400, 400, SkPaint());
+    });
+
+    auto chunks = dl->getChunks();
+    ASSERT_EQ(3u, chunks.size());
+
+    EXPECT_TRUE(chunks[0].reorderChildren);
+    EXPECT_EQ(nullptr, chunks[0].reorderClip);
+
+    EXPECT_FALSE(chunks[1].reorderChildren);
+    EXPECT_EQ(nullptr, chunks[1].reorderClip);
+
+    EXPECT_TRUE(chunks[2].reorderChildren);
+    ASSERT_NE(nullptr, chunks[2].reorderClip);
+    EXPECT_EQ(Rect(200, 200), chunks[2].reorderClip->rect);
+}
+
 TEST(RecordingCanvas, refPaint) {
     SkPaint paint;