Overdraw avoidance in new pipeline

bug:27873093

Adds the simple overdraw avoidance optimization to the new
pipeline. This means when LayerBuilder defers draws that are opaque over
the full area of the repaint region, it will discard all drawing content
beneth.

Also moves a lot of complexity out of BakedOpState's header.

Change-Id: Iffca6d8e1b170ef31a5d6c83d25592670e02323d
diff --git a/libs/hwui/BakedOpState.cpp b/libs/hwui/BakedOpState.cpp
index 85903654..b70d586 100644
--- a/libs/hwui/BakedOpState.cpp
+++ b/libs/hwui/BakedOpState.cpp
@@ -108,5 +108,63 @@
     clippedBounds.doIntersect(clipRect->rect);
 }
 
+BakedOpState* BakedOpState::tryConstruct(LinearAllocator& allocator,
+        Snapshot& snapshot, const RecordedOp& recordedOp) {
+    if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr;
+    BakedOpState* bakedState = allocator.create_trivial<BakedOpState>(
+            allocator, snapshot, recordedOp, false);
+    if (bakedState->computedState.clippedBounds.isEmpty()) {
+        // bounds are empty, so op is rejected
+        allocator.rewindIfLastAlloc(bakedState);
+        return nullptr;
+    }
+    return bakedState;
+}
+
+BakedOpState* BakedOpState::tryConstructUnbounded(LinearAllocator& allocator,
+        Snapshot& snapshot, const RecordedOp& recordedOp) {
+    if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr;
+    return allocator.create_trivial<BakedOpState>(allocator, snapshot, recordedOp);
+}
+
+BakedOpState* BakedOpState::tryStrokeableOpConstruct(LinearAllocator& allocator,
+        Snapshot& snapshot, const RecordedOp& recordedOp, StrokeBehavior strokeBehavior) {
+    if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr;
+    bool expandForStroke = (strokeBehavior == StrokeBehavior::StyleDefined)
+            ? (recordedOp.paint && recordedOp.paint->getStyle() != SkPaint::kFill_Style)
+            : true;
+
+    BakedOpState* bakedState = allocator.create_trivial<BakedOpState>(
+           allocator, snapshot, recordedOp, expandForStroke);
+    if (bakedState->computedState.clippedBounds.isEmpty()) {
+        // bounds are empty, so op is rejected
+        // NOTE: this won't succeed if a clip was allocated
+        allocator.rewindIfLastAlloc(bakedState);
+        return nullptr;
+    }
+    return bakedState;
+}
+
+BakedOpState* BakedOpState::tryShadowOpConstruct(LinearAllocator& allocator,
+        Snapshot& snapshot, const ShadowOp* shadowOpPtr) {
+    if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr;
+
+    // clip isn't empty, so construct the op
+    return allocator.create_trivial<BakedOpState>(allocator, snapshot, shadowOpPtr);
+}
+
+BakedOpState* BakedOpState::directConstruct(LinearAllocator& allocator,
+        const ClipRect* clip, const Rect& dstRect, const RecordedOp& recordedOp) {
+    return allocator.create_trivial<BakedOpState>(clip, dstRect, recordedOp);
+}
+
+void BakedOpState::setupOpacity(const SkPaint* paint) {
+    computedState.opaqueOverClippedBounds = computedState.transform.isSimple()
+            && computedState.clipState->mode == ClipMode::Rectangle
+            && MathUtils::areEqual(alpha, 1.0f)
+            && !roundRectClipState
+            && PaintUtils::isOpaquePaint(paint);
+}
+
 } // namespace uirenderer
 } // namespace android
diff --git a/libs/hwui/BakedOpState.h b/libs/hwui/BakedOpState.h
index 4e3cb8a..e1441fc 100644
--- a/libs/hwui/BakedOpState.h
+++ b/libs/hwui/BakedOpState.h
@@ -93,6 +93,7 @@
     Rect clippedBounds;
     int clipSideFlags = 0;
     const SkPath* localProjectionPathMask = nullptr;
+    bool opaqueOverClippedBounds = false;
 };
 
 /**
@@ -103,23 +104,10 @@
 class BakedOpState {
 public:
     static BakedOpState* tryConstruct(LinearAllocator& allocator,
-            Snapshot& snapshot, const RecordedOp& recordedOp) {
-        if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr;
-        BakedOpState* bakedState = allocator.create_trivial<BakedOpState>(
-                allocator, snapshot, recordedOp, false);
-        if (bakedState->computedState.clippedBounds.isEmpty()) {
-            // bounds are empty, so op is rejected
-            allocator.rewindIfLastAlloc(bakedState);
-            return nullptr;
-        }
-        return bakedState;
-    }
+            Snapshot& snapshot, const RecordedOp& recordedOp);
 
     static BakedOpState* tryConstructUnbounded(LinearAllocator& allocator,
-            Snapshot& snapshot, const RecordedOp& recordedOp) {
-        if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr;
-        return allocator.create_trivial<BakedOpState>(allocator, snapshot, recordedOp);
-    }
+            Snapshot& snapshot, const RecordedOp& recordedOp);
 
     enum class StrokeBehavior {
         // stroking is forced, regardless of style on paint (such as for lines)
@@ -129,35 +117,16 @@
     };
 
     static BakedOpState* tryStrokeableOpConstruct(LinearAllocator& allocator,
-            Snapshot& snapshot, const RecordedOp& recordedOp, StrokeBehavior strokeBehavior) {
-        if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr;
-        bool expandForStroke = (strokeBehavior == StrokeBehavior::StyleDefined)
-                ? (recordedOp.paint && recordedOp.paint->getStyle() != SkPaint::kFill_Style)
-                : true;
-
-        BakedOpState* bakedState = allocator.create_trivial<BakedOpState>(
-                allocator, snapshot, recordedOp, expandForStroke);
-        if (bakedState->computedState.clippedBounds.isEmpty()) {
-            // bounds are empty, so op is rejected
-            // NOTE: this won't succeed if a clip was allocated
-            allocator.rewindIfLastAlloc(bakedState);
-            return nullptr;
-        }
-        return bakedState;
-    }
+            Snapshot& snapshot, const RecordedOp& recordedOp, StrokeBehavior strokeBehavior);
 
     static BakedOpState* tryShadowOpConstruct(LinearAllocator& allocator,
-            Snapshot& snapshot, const ShadowOp* shadowOpPtr) {
-        if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr;
-
-        // clip isn't empty, so construct the op
-        return allocator.create_trivial<BakedOpState>(allocator, snapshot, shadowOpPtr);
-    }
+            Snapshot& snapshot, const ShadowOp* shadowOpPtr);
 
     static BakedOpState* directConstruct(LinearAllocator& allocator,
-            const ClipRect* clip, const Rect& dstRect, const RecordedOp& recordedOp) {
-        return allocator.create_trivial<BakedOpState>(clip, dstRect, recordedOp);
-    }
+            const ClipRect* clip, const Rect& dstRect, const RecordedOp& recordedOp);
+
+    // Set opaqueOverClippedBounds. If this method isn't called, the op is assumed translucent.
+    void setupOpacity(const SkPaint* paint);
 
     // computed state:
     ResolvedRenderState computedState;
diff --git a/libs/hwui/FrameBuilder.cpp b/libs/hwui/FrameBuilder.cpp
index b1314fe..b18836f 100644
--- a/libs/hwui/FrameBuilder.cpp
+++ b/libs/hwui/FrameBuilder.cpp
@@ -481,12 +481,17 @@
  * Defers an unmergeable, strokeable op, accounting correctly
  * for paint's style on the bounds being computed.
  */
-const BakedOpState* FrameBuilder::deferStrokeableOp(const RecordedOp& op, batchid_t batchId,
+BakedOpState* FrameBuilder::deferStrokeableOp(const RecordedOp& op, batchid_t batchId,
         BakedOpState::StrokeBehavior strokeBehavior) {
     // Note: here we account for stroke when baking the op
     BakedOpState* bakedState = BakedOpState::tryStrokeableOpConstruct(
             mAllocator, *mCanvasState.writableSnapshot(), op, strokeBehavior);
     if (!bakedState) return nullptr; // quick rejected
+
+    if (op.opId == RecordedOpId::RectOp && op.paint->getStyle() != SkPaint::kStroke_Style) {
+        bakedState->setupOpacity(op.paint);
+    }
+
     currentLayer().deferUnmergeableOp(mAllocator, bakedState, batchId);
     return bakedState;
 }
@@ -516,6 +521,7 @@
 void FrameBuilder::deferBitmapOp(const BitmapOp& op) {
     BakedOpState* bakedState = tryBakeOpState(op);
     if (!bakedState) return; // quick rejected
+    bakedState->setupOpacity(op.paint);
 
     // Don't merge non-simply transformed or neg scale ops, SET_TEXTURE doesn't handle rotation
     // Don't merge A8 bitmaps - the paint's color isn't compared by mergeId, or in
diff --git a/libs/hwui/FrameBuilder.h b/libs/hwui/FrameBuilder.h
index 0b7a606..02c05cb 100644
--- a/libs/hwui/FrameBuilder.h
+++ b/libs/hwui/FrameBuilder.h
@@ -201,7 +201,7 @@
         return mAllocator.create<SkPath>();
     }
 
-    const BakedOpState* deferStrokeableOp(const RecordedOp& op, batchid_t batchId,
+    BakedOpState* deferStrokeableOp(const RecordedOp& op, batchid_t batchId,
             BakedOpState::StrokeBehavior strokeBehavior = BakedOpState::StrokeBehavior::StyleDefined);
 
     /**
diff --git a/libs/hwui/LayerBuilder.cpp b/libs/hwui/LayerBuilder.cpp
index e6a95ff..eea11bf 100644
--- a/libs/hwui/LayerBuilder.cpp
+++ b/libs/hwui/LayerBuilder.cpp
@@ -236,6 +236,21 @@
     mClearRects.push_back(rect);
 }
 
+void LayerBuilder::onDeferOp(LinearAllocator& allocator, const BakedOpState* bakedState) {
+    if (bakedState->op->opId != RecordedOpId::CopyToLayerOp) {
+        // First non-CopyToLayer, so stop stashing up layer clears for unclipped save layers,
+        // and issue them together in one draw.
+        flushLayerClears(allocator);
+
+        if (CC_UNLIKELY(activeUnclippedSaveLayers.empty()
+                && bakedState->computedState.opaqueOverClippedBounds
+                && bakedState->computedState.clippedBounds.contains(repaintRect))) {
+            // discard all deferred drawing ops, since new one will occlude them
+            clear();
+        }
+    }
+}
+
 void LayerBuilder::flushLayerClears(LinearAllocator& allocator) {
     if (CC_UNLIKELY(!mClearRects.empty())) {
         const int vertCount = mClearRects.size() * 4;
@@ -270,11 +285,7 @@
 
 void LayerBuilder::deferUnmergeableOp(LinearAllocator& allocator,
         BakedOpState* op, batchid_t batchId) {
-    if (batchId != OpBatchType::CopyToLayer) {
-        // if first op after one or more unclipped saveLayers, flush the layer clears
-        flushLayerClears(allocator);
-    }
-
+    onDeferOp(allocator, op);
     OpBatch* targetBatch = mBatchLookup[batchId];
 
     size_t insertBatchIndex = mBatches.size();
@@ -295,10 +306,7 @@
 
 void LayerBuilder::deferMergeableOp(LinearAllocator& allocator,
         BakedOpState* op, batchid_t batchId, mergeid_t mergeId) {
-    if (batchId != OpBatchType::CopyToLayer) {
-        // if first op after one or more unclipped saveLayers, flush the layer clears
-        flushLayerClears(allocator);
-    }
+    onDeferOp(allocator, op);
     MergingOpBatch* targetBatch = nullptr;
 
     // Try to merge with any existing batch with same mergeId
@@ -348,6 +356,14 @@
     }
 }
 
+void LayerBuilder::clear() {
+    mBatches.clear();
+    for (int i = 0; i < OpBatchType::Count; i++) {
+        mBatchLookup[i] = nullptr;
+        mMergingBatchLookup[i].clear();
+    }
+}
+
 void LayerBuilder::dump() const {
     ALOGD("LayerBuilder %p, %ux%u buffer %p, blo %p, rn %p (%s)",
             this, width, height, offscreenBuffer, beginLayerOp,
diff --git a/libs/hwui/LayerBuilder.h b/libs/hwui/LayerBuilder.h
index 4a7ca2d..4de432c 100644
--- a/libs/hwui/LayerBuilder.h
+++ b/libs/hwui/LayerBuilder.h
@@ -100,9 +100,7 @@
         return mBatches.empty();
     }
 
-    void clear() {
-        mBatches.clear();
-    }
+    void clear();
 
     void dump() const;
 
@@ -117,6 +115,7 @@
     // list of deferred CopyFromLayer ops, to be deferred upon encountering EndUnclippedLayerOps
     std::vector<BakedOpState*> activeUnclippedSaveLayers;
 private:
+    void onDeferOp(LinearAllocator& allocator, const BakedOpState* bakedState);
     void flushLayerClears(LinearAllocator& allocator);
 
     std::vector<BatchBase*> mBatches;
diff --git a/libs/hwui/tests/unit/FrameBuilderTests.cpp b/libs/hwui/tests/unit/FrameBuilderTests.cpp
index e97aaa6..ba22f91 100644
--- a/libs/hwui/tests/unit/FrameBuilderTests.cpp
+++ b/libs/hwui/tests/unit/FrameBuilderTests.cpp
@@ -216,6 +216,80 @@
             << "Expect number of ops = 2 * loop count";
 }
 
+RENDERTHREAD_TEST(FrameBuilder, avoidOverdraw_rects) {
+    class AvoidOverdrawRectsTestRenderer : public TestRendererBase {
+    public:
+        void onRectOp(const RectOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(mIndex++, 0) << "Should be one rect";
+            EXPECT_EQ(Rect(10, 10, 190, 190), op.unmappedBounds)
+                    << "Last rect should occlude others.";
+        }
+    };
+    auto node = TestUtils::createNode(0, 0, 200, 200,
+            [](RenderProperties& props, RecordingCanvas& canvas) {
+        canvas.drawRect(0, 0, 200, 200, SkPaint());
+        canvas.drawRect(0, 0, 200, 200, SkPaint());
+        canvas.drawRect(10, 10, 190, 190, SkPaint());
+    });
+
+    // Damage (and therefore clip) is same as last draw, subset of renderable area.
+    // This means last op occludes other contents, and they'll be rejected to avoid overdraw.
+    SkRect damageRect = SkRect::MakeLTRB(10, 10, 190, 190);
+    FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, damageRect, 200, 200,
+            TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance());
+
+    EXPECT_EQ(3u, node->getDisplayList()->getOps().size())
+            << "Recording must not have rejected ops, in order for this test to be valid";
+
+    AvoidOverdrawRectsTestRenderer renderer;
+    frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(1, renderer.getIndex()) << "Expect exactly one op";
+}
+
+RENDERTHREAD_TEST(FrameBuilder, avoidOverdraw_bitmaps) {
+    static SkBitmap opaqueBitmap = TestUtils::createSkBitmap(50, 50,
+            SkColorType::kRGB_565_SkColorType);
+    static SkBitmap transpBitmap = TestUtils::createSkBitmap(50, 50,
+            SkColorType::kAlpha_8_SkColorType);
+    class AvoidOverdrawBitmapsTestRenderer : public TestRendererBase {
+    public:
+        void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override {
+            EXPECT_LT(mIndex++, 2) << "Should be two bitmaps";
+            switch(mIndex++) {
+            case 0:
+                EXPECT_EQ(opaqueBitmap.pixelRef(), op.bitmap->pixelRef());
+                break;
+            case 1:
+                EXPECT_EQ(transpBitmap.pixelRef(), op.bitmap->pixelRef());
+                break;
+            default:
+                ADD_FAILURE() << "Only two ops expected.";
+            }
+        }
+    };
+
+    auto node = TestUtils::createNode(0, 0, 50, 50,
+            [](RenderProperties& props, RecordingCanvas& canvas) {
+        canvas.drawRect(0, 0, 50, 50, SkPaint());
+        canvas.drawRect(0, 0, 50, 50, SkPaint());
+        canvas.drawBitmap(transpBitmap, 0, 0, nullptr);
+
+        // only the below draws should remain, since they're
+        canvas.drawBitmap(opaqueBitmap, 0, 0, nullptr);
+        canvas.drawBitmap(transpBitmap, 0, 0, nullptr);
+    });
+
+    FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(50, 50), 50, 50,
+            TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance());
+
+    EXPECT_EQ(5u, node->getDisplayList()->getOps().size())
+            << "Recording must not have rejected ops, in order for this test to be valid";
+
+    AvoidOverdrawBitmapsTestRenderer renderer;
+    frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(2, renderer.getIndex()) << "Expect exactly one op";
+}
+
 RENDERTHREAD_TEST(FrameBuilder, clippedMerging) {
     class ClippedMergingTestRenderer : public TestRendererBase {
     public:
diff --git a/libs/hwui/utils/PaintUtils.h b/libs/hwui/utils/PaintUtils.h
index db53713..4faab9a 100644
--- a/libs/hwui/utils/PaintUtils.h
+++ b/libs/hwui/utils/PaintUtils.h
@@ -67,6 +67,21 @@
                 && getXfermode(paint.getXfermode()) == SkXfermode::kSrcOver_Mode;
     }
 
+    static bool isOpaquePaint(const SkPaint* paint) {
+        if (!paint) return true; // default (paintless) behavior is SrcOver, black
+
+        if (paint->getAlpha() != 0xFF
+                || PaintUtils::isBlendedShader(paint->getShader())
+                || PaintUtils::isBlendedColorFilter(paint->getColorFilter())) {
+            return false;
+        }
+
+        // Only let simple srcOver / src blending modes declare opaque, since behavior is clear.
+        SkXfermode::Mode mode = getXfermode(paint->getXfermode());
+        return mode == SkXfermode::Mode::kSrcOver_Mode
+                || mode == SkXfermode::Mode::kSrc_Mode;
+    }
+
     static bool isBlendedShader(const SkShader* shader) {
         if (shader == nullptr) {
             return false;