Merge "Do not cache AVDs that are off screen" into qt-dev
diff --git a/libs/hwui/TreeInfo.cpp b/libs/hwui/TreeInfo.cpp
index cdad20e..dc53dd6 100644
--- a/libs/hwui/TreeInfo.cpp
+++ b/libs/hwui/TreeInfo.cpp
@@ -25,6 +25,7 @@
         , prepareTextures(mode == MODE_FULL)
         , canvasContext(canvasContext)
         , damageGenerationId(canvasContext.getFrameNumber())
-        , disableForceDark(canvasContext.useForceDark() ? 0 : 1) {}
+        , disableForceDark(canvasContext.useForceDark() ? 0 : 1)
+        , screenSize(canvasContext.getNextFrameSize()) {}
 
 }  // namespace android::uirenderer
diff --git a/libs/hwui/TreeInfo.h b/libs/hwui/TreeInfo.h
index 04eabac..7e8d12f 100644
--- a/libs/hwui/TreeInfo.h
+++ b/libs/hwui/TreeInfo.h
@@ -20,6 +20,7 @@
 #include "utils/Macros.h"
 
 #include <utils/Timers.h>
+#include "SkSize.h"
 
 #include <string>
 
@@ -96,6 +97,8 @@
 
     int disableForceDark;
 
+    const SkISize screenSize;
+
     struct Out {
         bool hasFunctors = false;
         // This is only updated if evaluateAnimations is true
diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp
index da905cf..5418b33 100644
--- a/libs/hwui/VectorDrawable.cpp
+++ b/libs/hwui/VectorDrawable.cpp
@@ -547,6 +547,11 @@
 }
 
 void Tree::draw(SkCanvas* canvas, const SkRect& bounds, const SkPaint& inPaint) {
+    if (canvas->quickReject(bounds)) {
+        // The RenderNode is on screen, but the AVD is not.
+        return;
+    }
+
     // Update the paint for any animatable properties
     SkPaint paint = inPaint;
     paint.setAlpha(mProperties.getRootAlpha() * 255);
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
index 29d5ef2..41bcfc2 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
@@ -22,6 +22,7 @@
 #include "renderthread/CanvasContext.h"
 
 #include <SkImagePriv.h>
+#include <SkPathOps.h>
 
 namespace android {
 namespace uirenderer {
@@ -35,7 +36,7 @@
         animatedImage->syncProperties();
     }
     for (auto& vectorDrawable : mVectorDrawables) {
-        vectorDrawable->syncProperties();
+        vectorDrawable.first->syncProperties();
     }
 }
 
@@ -51,6 +52,29 @@
     }
 }
 
+static bool intersects(const SkISize screenSize, const Matrix4& mat, const SkRect& bounds) {
+    Vector3 points[] = { Vector3 {bounds.fLeft, bounds.fTop, 0},
+                         Vector3 {bounds.fRight, bounds.fTop, 0},
+                         Vector3 {bounds.fRight, bounds.fBottom, 0},
+                         Vector3 {bounds.fLeft, bounds.fBottom, 0}};
+    float minX, minY, maxX, maxY;
+    bool first = true;
+    for (auto& point : points) {
+        mat.mapPoint3d(point);
+        if (first) {
+            minX = maxX = point.x;
+            minY = maxY = point.y;
+            first = false;
+        } else {
+            minX = std::min(minX, point.x);
+            minY = std::min(minY, point.y);
+            maxX = std::max(maxX, point.x);
+            maxY = std::max(maxY, point.y);
+        }
+    }
+    return SkRect::Make(screenSize).intersects(SkRect::MakeLTRB(minX, minY, maxX, maxY));
+}
+
 bool SkiaDisplayList::prepareListAndChildren(
         TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer,
         std::function<void(RenderNode*, TreeObserver&, TreeInfo&, bool)> childFn) {
@@ -107,15 +131,23 @@
         }
     }
 
-    for (auto& vectorDrawable : mVectorDrawables) {
+    for (auto& vectorDrawablePair : mVectorDrawables) {
         // If any vector drawable in the display list needs update, damage the node.
+        auto& vectorDrawable = vectorDrawablePair.first;
         if (vectorDrawable->isDirty()) {
-            isDirty = true;
-            static_cast<SkiaPipeline*>(info.canvasContext.getRenderPipeline())
-                    ->getVectorDrawables()
-                    ->push_back(vectorDrawable);
+            Matrix4 totalMatrix;
+            info.damageAccumulator->computeCurrentTransform(&totalMatrix);
+            Matrix4 canvasMatrix(vectorDrawablePair.second);
+            totalMatrix.multiply(canvasMatrix);
+            const SkRect& bounds = vectorDrawable->properties().getBounds();
+            if (intersects(info.screenSize, totalMatrix, bounds)) {
+                isDirty = true;
+                static_cast<SkiaPipeline*>(info.canvasContext.getRenderPipeline())
+                        ->getVectorDrawables()
+                        ->push_back(vectorDrawable);
+                vectorDrawable->setPropertyChangeWillBeConsumed(true);
+            }
         }
-        vectorDrawable->setPropertyChangeWillBeConsumed(true);
     }
     return isDirty;
 }
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.h b/libs/hwui/pipeline/skia/SkiaDisplayList.h
index 3219ad1..b791037 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.h
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.h
@@ -22,6 +22,7 @@
 #include "TreeInfo.h"
 #include "hwui/AnimatedImageDrawable.h"
 #include "utils/LinearAllocator.h"
+#include "utils/Pair.h"
 
 #include <deque>
 
@@ -41,12 +42,6 @@
 
 namespace skiapipeline {
 
-/**
- * This class is intended to be self contained, but still subclasses from
- * DisplayList to make it easier to support switching between the two at
- * runtime.  The downside of this inheritance is that we pay for the overhead
- * of the parent class construction/destruction without any real benefit.
- */
 class SkiaDisplayList {
 public:
     size_t getUsedSize() { return allocator.usedSize() + mDisplayList.usedSize(); }
@@ -156,7 +151,17 @@
     std::deque<RenderNodeDrawable> mChildNodes;
     std::deque<FunctorDrawable*> mChildFunctors;
     std::vector<SkImage*> mMutableImages;
-    std::vector<VectorDrawableRoot*> mVectorDrawables;
+private:
+    std::vector<Pair<VectorDrawableRoot*, SkMatrix>> mVectorDrawables;
+public:
+    void appendVD(VectorDrawableRoot* r) {
+        appendVD(r, SkMatrix::I());
+    }
+
+    void appendVD(VectorDrawableRoot* r, const SkMatrix& mat) {
+        mVectorDrawables.push_back(Pair<VectorDrawableRoot*, SkMatrix>(r, mat));
+    }
+
     std::vector<AnimatedImageDrawable*> mAnimatedImages;
     DisplayListData mDisplayList;
 
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index 5a47a29..0a28949 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -152,7 +152,9 @@
 
 void SkiaRecordingCanvas::drawVectorDrawable(VectorDrawableRoot* tree) {
     mRecorder.drawVectorDrawable(tree);
-    mDisplayList->mVectorDrawables.push_back(tree);
+    SkMatrix mat;
+    this->getMatrix(&mat);
+    mDisplayList->appendVD(tree, mat);
 }
 
 // ----------------------------------------------------------------------------
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 1b3bd30..2957b14 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -41,6 +41,7 @@
 #include <sys/stat.h>
 #include <algorithm>
 
+#include <cstdint>
 #include <cstdlib>
 #include <functional>
 
@@ -510,6 +511,17 @@
     prepareAndDraw(nullptr);
 }
 
+SkISize CanvasContext::getNextFrameSize() const {
+    ReliableSurface* surface = mNativeSurface.get();
+    if (surface) {
+        SkISize size;
+        surface->query(NATIVE_WINDOW_WIDTH, &size.fWidth);
+        surface->query(NATIVE_WINDOW_HEIGHT, &size.fHeight);
+        return size;
+    }
+    return {INT32_MAX, INT32_MAX};
+}
+
 void CanvasContext::prepareAndDraw(RenderNode* node) {
     ATRACE_CALL();
 
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 0bd080d..912b125 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -32,6 +32,7 @@
 #include <EGL/egl.h>
 #include <SkBitmap.h>
 #include <SkRect.h>
+#include <SkSize.h>
 #include <cutils/compiler.h>
 #include <gui/Surface.h>
 #include <utils/Functor.h>
@@ -112,7 +113,7 @@
     void setSurface(sp<Surface>&& surface);
     bool pauseSurface();
     void setStopped(bool stopped);
-    bool hasSurface() { return mNativeSurface.get(); }
+    bool hasSurface() const { return mNativeSurface.get(); }
     void allocateBuffers();
 
     void setLightAlpha(uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha);
@@ -205,6 +206,8 @@
     // Must be called before setSurface
     void setRenderAheadDepth(uint32_t renderAhead);
 
+    SkISize getNextFrameSize() const;
+
 private:
     CanvasContext(RenderThread& thread, bool translucent, RenderNode* rootRenderNode,
                   IContextFactory* contextFactory, std::unique_ptr<IRenderPipeline> renderPipeline);
diff --git a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
index 1b4cf7e..6fb164a 100644
--- a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
+++ b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
@@ -23,6 +23,7 @@
 #include "pipeline/skia/GLFunctorDrawable.h"
 #include "pipeline/skia/SkiaDisplayList.h"
 #include "renderthread/CanvasContext.h"
+#include "tests/common/TestContext.h"
 #include "tests/common/TestUtils.h"
 
 using namespace android;
@@ -50,13 +51,13 @@
     GLFunctorDrawable functorDrawable(nullptr, nullptr, &dummyCanvas);
     skiaDL->mChildFunctors.push_back(&functorDrawable);
     skiaDL->mMutableImages.push_back(nullptr);
-    skiaDL->mVectorDrawables.push_back(nullptr);
+    skiaDL->appendVD(nullptr);
     skiaDL->mProjectionReceiver = &drawable;
 
     ASSERT_FALSE(skiaDL->mChildNodes.empty());
     ASSERT_FALSE(skiaDL->mChildFunctors.empty());
     ASSERT_FALSE(skiaDL->mMutableImages.empty());
-    ASSERT_FALSE(skiaDL->mVectorDrawables.empty());
+    ASSERT_TRUE(skiaDL->hasVectorDrawables());
     ASSERT_FALSE(skiaDL->isEmpty());
     ASSERT_TRUE(skiaDL->mProjectionReceiver);
 
@@ -65,7 +66,7 @@
     ASSERT_TRUE(skiaDL->mChildNodes.empty());
     ASSERT_TRUE(skiaDL->mChildFunctors.empty());
     ASSERT_TRUE(skiaDL->mMutableImages.empty());
-    ASSERT_TRUE(skiaDL->mVectorDrawables.empty());
+    ASSERT_FALSE(skiaDL->hasVectorDrawables());
     ASSERT_TRUE(skiaDL->isEmpty());
     ASSERT_FALSE(skiaDL->mProjectionReceiver);
 }
@@ -110,7 +111,7 @@
     SkRect bounds = SkRect::MakeWH(200, 200);
     VectorDrawableRoot vectorDrawable(new VectorDrawable::Group());
     vectorDrawable.mutateStagingProperties()->setBounds(bounds);
-    skiaDL.mVectorDrawables.push_back(&vectorDrawable);
+    skiaDL.appendVD(&vectorDrawable);
 
     // ensure that the functor and vectorDrawable are properly synced
     TestUtils::runOnRenderThread([&](auto&) {
@@ -149,9 +150,14 @@
 
     SkiaDisplayList skiaDL;
 
+    // The VectorDrawableRoot needs to have bounds on screen (and therefore not
+    // empty) in order to have PropertyChangeWillBeConsumed set.
+    const auto bounds = SkRect::MakeIWH(100, 100);
+
     // prepare with a clean VD
     VectorDrawableRoot cleanVD(new VectorDrawable::Group());
-    skiaDL.mVectorDrawables.push_back(&cleanVD);
+    cleanVD.mutateProperties()->setBounds(bounds);
+    skiaDL.appendVD(&cleanVD);
     cleanVD.getBitmapUpdateIfDirty();  // this clears the dirty bit
 
     ASSERT_FALSE(cleanVD.isDirty());
@@ -159,11 +165,12 @@
     TestUtils::MockTreeObserver observer;
     ASSERT_FALSE(skiaDL.prepareListAndChildren(observer, info, false,
                                                [](RenderNode*, TreeObserver&, TreeInfo&, bool) {}));
-    ASSERT_TRUE(cleanVD.getPropertyChangeWillBeConsumed());
+    ASSERT_FALSE(cleanVD.getPropertyChangeWillBeConsumed());
 
     // prepare again this time adding a dirty VD
     VectorDrawableRoot dirtyVD(new VectorDrawable::Group());
-    skiaDL.mVectorDrawables.push_back(&dirtyVD);
+    dirtyVD.mutateProperties()->setBounds(bounds);
+    skiaDL.appendVD(&dirtyVD);
 
     ASSERT_TRUE(dirtyVD.isDirty());
     ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed());
@@ -191,6 +198,169 @@
     canvasContext->destroy();
 }
 
+RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaDisplayList, prepareListAndChildren_vdOffscreen) {
+    auto rootNode = TestUtils::createNode(0, 0, 200, 400, nullptr);
+    ContextFactory contextFactory;
+    std::unique_ptr<CanvasContext> canvasContext(
+            CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory));
+
+    // Set up a Surface so that we can position the VectorDrawable offscreen.
+    test::TestContext testContext;
+    testContext.setRenderOffscreen(true);
+    auto surface = testContext.surface();
+    int width, height;
+    surface->query(NATIVE_WINDOW_WIDTH, &width);
+    surface->query(NATIVE_WINDOW_HEIGHT, &height);
+    canvasContext->setSurface(std::move(surface));
+
+    TreeInfo info(TreeInfo::MODE_FULL, *canvasContext.get());
+    DamageAccumulator damageAccumulator;
+    info.damageAccumulator = &damageAccumulator;
+
+    // The VectorDrawableRoot needs to have bounds on screen (and therefore not
+    // empty) in order to have PropertyChangeWillBeConsumed set.
+    const auto bounds = SkRect::MakeIWH(100, 100);
+
+    for (const SkRect b : {bounds.makeOffset(width, 0),
+                           bounds.makeOffset(0, height),
+                           bounds.makeOffset(-bounds.width(), 0),
+                           bounds.makeOffset(0, -bounds.height())}) {
+        SkiaDisplayList skiaDL;
+        VectorDrawableRoot dirtyVD(new VectorDrawable::Group());
+        dirtyVD.mutateProperties()->setBounds(b);
+        skiaDL.appendVD(&dirtyVD);
+
+        ASSERT_TRUE(dirtyVD.isDirty());
+        ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed());
+
+        TestUtils::MockTreeObserver observer;
+        ASSERT_FALSE(skiaDL.prepareListAndChildren(
+                observer, info, false, [](RenderNode*, TreeObserver&, TreeInfo&, bool) {}));
+        ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed());
+    }
+
+    // The DamageAccumulator's transform can also result in the
+    // VectorDrawableRoot being offscreen.
+    for (const SkISize translate : { SkISize{width, 0},
+                                     SkISize{0, height},
+                                     SkISize{-width, 0},
+                                     SkISize{0, -height}}) {
+        Matrix4 mat4;
+        mat4.translate(translate.fWidth, translate.fHeight);
+        damageAccumulator.pushTransform(&mat4);
+
+        SkiaDisplayList skiaDL;
+        VectorDrawableRoot dirtyVD(new VectorDrawable::Group());
+        dirtyVD.mutateProperties()->setBounds(bounds);
+        skiaDL.appendVD(&dirtyVD);
+
+        ASSERT_TRUE(dirtyVD.isDirty());
+        ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed());
+
+        TestUtils::MockTreeObserver observer;
+        ASSERT_FALSE(skiaDL.prepareListAndChildren(
+                observer, info, false, [](RenderNode*, TreeObserver&, TreeInfo&, bool) {}));
+        ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed());
+        damageAccumulator.popTransform();
+    }
+
+    // Another way to be offscreen: a matrix from the draw call.
+    for (const SkMatrix translate : { SkMatrix::MakeTrans(width, 0),
+                                      SkMatrix::MakeTrans(0, height),
+                                      SkMatrix::MakeTrans(-width, 0),
+                                      SkMatrix::MakeTrans(0, -height)}) {
+        SkiaDisplayList skiaDL;
+        VectorDrawableRoot dirtyVD(new VectorDrawable::Group());
+        dirtyVD.mutateProperties()->setBounds(bounds);
+        skiaDL.appendVD(&dirtyVD, translate);
+
+        ASSERT_TRUE(dirtyVD.isDirty());
+        ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed());
+
+        TestUtils::MockTreeObserver observer;
+        ASSERT_FALSE(skiaDL.prepareListAndChildren(
+                observer, info, false, [](RenderNode*, TreeObserver&, TreeInfo&, bool) {}));
+        ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed());
+    }
+
+    // Verify that the matrices are combined in the right order.
+    {
+        // Rotate and then translate, so the VD is offscreen.
+        Matrix4 mat4;
+        mat4.loadRotate(180);
+        damageAccumulator.pushTransform(&mat4);
+
+        SkiaDisplayList skiaDL;
+        VectorDrawableRoot dirtyVD(new VectorDrawable::Group());
+        dirtyVD.mutateProperties()->setBounds(bounds);
+        SkMatrix translate = SkMatrix::MakeTrans(50, 50);
+        skiaDL.appendVD(&dirtyVD, translate);
+
+        ASSERT_TRUE(dirtyVD.isDirty());
+        ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed());
+
+        TestUtils::MockTreeObserver observer;
+        ASSERT_FALSE(skiaDL.prepareListAndChildren(
+                observer, info, false, [](RenderNode*, TreeObserver&, TreeInfo&, bool) {}));
+        ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed());
+        damageAccumulator.popTransform();
+    }
+    {
+        // Switch the order of rotate and translate, so it is on screen.
+        Matrix4 mat4;
+        mat4.translate(50, 50);
+        damageAccumulator.pushTransform(&mat4);
+
+        SkiaDisplayList skiaDL;
+        VectorDrawableRoot dirtyVD(new VectorDrawable::Group());
+        dirtyVD.mutateProperties()->setBounds(bounds);
+        SkMatrix rotate;
+        rotate.setRotate(180);
+        skiaDL.appendVD(&dirtyVD, rotate);
+
+        ASSERT_TRUE(dirtyVD.isDirty());
+        ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed());
+
+        TestUtils::MockTreeObserver observer;
+        ASSERT_TRUE(skiaDL.prepareListAndChildren(
+                observer, info, false, [](RenderNode*, TreeObserver&, TreeInfo&, bool) {}));
+        ASSERT_TRUE(dirtyVD.getPropertyChangeWillBeConsumed());
+        damageAccumulator.popTransform();
+    }
+    {
+        // An AVD that is larger than the screen.
+        SkiaDisplayList skiaDL;
+        VectorDrawableRoot dirtyVD(new VectorDrawable::Group());
+        dirtyVD.mutateProperties()->setBounds(SkRect::MakeLTRB(-1, -1, width + 1, height + 1));
+        skiaDL.appendVD(&dirtyVD);
+
+        ASSERT_TRUE(dirtyVD.isDirty());
+        ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed());
+
+        TestUtils::MockTreeObserver observer;
+        ASSERT_TRUE(skiaDL.prepareListAndChildren(
+                observer, info, false, [](RenderNode*, TreeObserver&, TreeInfo&, bool) {}));
+        ASSERT_TRUE(dirtyVD.getPropertyChangeWillBeConsumed());
+    }
+    {
+        // An AVD whose bounds are not a rectangle after applying a matrix.
+        SkiaDisplayList skiaDL;
+        VectorDrawableRoot dirtyVD(new VectorDrawable::Group());
+        dirtyVD.mutateProperties()->setBounds(bounds);
+        SkMatrix mat;
+        mat.setRotate(45, 50, 50);
+        skiaDL.appendVD(&dirtyVD, mat);
+
+        ASSERT_TRUE(dirtyVD.isDirty());
+        ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed());
+
+        TestUtils::MockTreeObserver observer;
+        ASSERT_TRUE(skiaDL.prepareListAndChildren(
+                observer, info, false, [](RenderNode*, TreeObserver&, TreeInfo&, bool) {}));
+        ASSERT_TRUE(dirtyVD.getPropertyChangeWillBeConsumed());
+    }
+}
+
 TEST(SkiaDisplayList, updateChildren) {
     SkiaDisplayList skiaDL;