Merge "Do not cache AVDs that are off screen" into qt-dev

This commit is contained in:
Leon Scroggins
2019-04-16 12:15:16 +00:00
committed by Android (Google) Code Review
9 changed files with 257 additions and 24 deletions

View File

@@ -25,6 +25,7 @@ TreeInfo::TreeInfo(TraversalMode mode, renderthread::CanvasContext& canvasContex
, 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

View File

@@ -20,6 +20,7 @@
#include "utils/Macros.h"
#include <utils/Timers.h>
#include "SkSize.h"
#include <string>
@@ -96,6 +97,8 @@ public:
int disableForceDark;
const SkISize screenSize;
struct Out {
bool hasFunctors = false;
// This is only updated if evaluateAnimations is true

View File

@@ -547,6 +547,11 @@ void Tree::Cache::clear() {
}
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);

View File

@@ -22,6 +22,7 @@
#include "renderthread/CanvasContext.h"
#include <SkImagePriv.h>
#include <SkPathOps.h>
namespace android {
namespace uirenderer {
@@ -35,7 +36,7 @@ void SkiaDisplayList::syncContents(const WebViewSyncData& data) {
animatedImage->syncProperties();
}
for (auto& vectorDrawable : mVectorDrawables) {
vectorDrawable->syncProperties();
vectorDrawable.first->syncProperties();
}
}
@@ -51,6 +52,29 @@ void SkiaDisplayList::updateChildren(std::function<void(RenderNode*)> updateFn)
}
}
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 @@ bool SkiaDisplayList::prepareListAndChildren(
}
}
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;
}

View File

@@ -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 @@ typedef uirenderer::VectorDrawable::Tree VectorDrawableRoot;
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 @@ public:
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;

View File

@@ -152,7 +152,9 @@ void SkiaRecordingCanvas::drawWebViewFunctor(int functor) {
void SkiaRecordingCanvas::drawVectorDrawable(VectorDrawableRoot* tree) {
mRecorder.drawVectorDrawable(tree);
mDisplayList->mVectorDrawables.push_back(tree);
SkMatrix mat;
this->getMatrix(&mat);
mDisplayList->appendVD(tree, mat);
}
// ----------------------------------------------------------------------------

View File

@@ -41,6 +41,7 @@
#include <sys/stat.h>
#include <algorithm>
#include <cstdint>
#include <cstdlib>
#include <functional>
@@ -510,6 +511,17 @@ void CanvasContext::doFrame() {
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();

View File

@@ -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 @@ public:
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 @@ public:
// 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);

View File

@@ -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 @@ TEST(SkiaDisplayList, reset) {
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 @@ TEST(SkiaDisplayList, reset) {
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 @@ TEST(SkiaDisplayList, syncContexts) {
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 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaDisplayList, prepareListAndChildren) {
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 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaDisplayList, prepareListAndChildren) {
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 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaDisplayList, prepareListAndChildren) {
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;