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;