Merge "Fix Skia render node projection to match HWUI"

This commit is contained in:
TreeHugger Robot
2016-11-22 23:43:21 +00:00
committed by Android (Google) Code Review
10 changed files with 1150 additions and 136 deletions

View File

@@ -181,6 +181,7 @@ hwui_c_includes += \
external/skia/include/private \
external/skia/src/core \
external/skia/src/effects \
external/skia/src/image \
external/harfbuzz_ng/src \
external/freetype/include

View File

@@ -116,6 +116,9 @@ public:
bool canDrawThisFrame = true;
} out;
// This flag helps to disable projection for receiver nodes that do not have any backward
// projected children.
bool hasBackwardProjectedNodes = false;
// TODO: Damage calculations
};

View File

@@ -24,8 +24,41 @@ namespace android {
namespace uirenderer {
namespace skiapipeline {
void RenderNodeDrawable::drawBackwardsProjectedNodes(SkCanvas* canvas, const SkiaDisplayList& displayList,
int nestLevel) {
LOG_ALWAYS_FATAL_IF(0 == nestLevel && !displayList.mProjectionReceiver);
for (auto& child : displayList.mChildNodes) {
const RenderProperties& childProperties = child.getNodeProperties();
//immediate children cannot be projected on their parent
if (childProperties.getProjectBackwards() && nestLevel > 0) {
SkAutoCanvasRestore acr2(canvas, true);
//Apply recorded matrix, which is a total matrix saved at recording time to avoid
//replaying all DL commands.
canvas->concat(child.getRecordedMatrix());
child.drawContent(canvas);
}
//skip walking sub-nodes if current display list contains a receiver with exception of
//level 0, which is a known receiver
if (0 == nestLevel || !displayList.containsProjectionReceiver()) {
SkAutoCanvasRestore acr(canvas, true);
SkMatrix nodeMatrix;
mat4 hwuiMatrix(child.getRecordedMatrix());
auto childNode = child.getRenderNode();
childNode->applyViewPropertyTransforms(hwuiMatrix);
hwuiMatrix.copyTo(nodeMatrix);
canvas->concat(nodeMatrix);
SkiaDisplayList* childDisplayList = static_cast<SkiaDisplayList*>(
(const_cast<DisplayList*>(childNode->getDisplayList())));
if (childDisplayList) {
drawBackwardsProjectedNodes(canvas, *childDisplayList, nestLevel+1);
}
}
}
}
static void clipOutline(const Outline& outline, SkCanvas* canvas, const SkRect* pendingClip) {
SkASSERT(outline.willClip());
Rect possibleRect;
float radius;
LOG_ALWAYS_FATAL_IF(!outline.getAsRoundRect(&possibleRect, &radius),
@@ -74,53 +107,25 @@ void RenderNodeDrawable::forceDraw(SkCanvas* canvas) {
SkiaDisplayList* displayList = (SkiaDisplayList*)renderNode->getDisplayList();
SkAutoCanvasRestore acr(canvas, true);
const RenderProperties& properties = this->getNodeProperties();
if (displayList->mIsProjectionReceiver) {
// this node is a projection receiver. We will gather the projected nodes as we draw our
// children, and then draw them on top of this node's content.
std::vector<ProjectedChild> newList;
for (auto& child : displayList->mChildNodes) {
// our direct children are not supposed to project into us (nodes project to, at the
// nearest, their grandparents). So we "delay" the list's activation one level by
// passing it into mNextProjectedChildrenTarget rather than mProjectedChildrenTarget.
child.mProjectedChildrenTarget = mNextProjectedChildrenTarget;
child.mNextProjectedChildrenTarget = &newList;
//pass this outline to the children that may clip backward projected nodes
displayList->mProjectedOutline = displayList->containsProjectionReceiver()
? &properties.getOutline() : nullptr;
if (!properties.getProjectBackwards()) {
drawContent(canvas);
if (mProjectedDisplayList) {
acr.restore(); //draw projected children using parent matrix
LOG_ALWAYS_FATAL_IF(!mProjectedDisplayList->mProjectedOutline);
const bool shouldClip = mProjectedDisplayList->mProjectedOutline->getPath();
SkAutoCanvasRestore acr2(canvas, shouldClip);
canvas->setMatrix(mProjectedDisplayList->mProjectedReceiverParentMatrix);
if (shouldClip) {
clipOutline(*mProjectedDisplayList->mProjectedOutline, canvas, nullptr);
}
drawBackwardsProjectedNodes(canvas, *mProjectedDisplayList);
}
// draw ourselves and our children. As a side effect, this will add projected nodes to
// newList.
this->drawContent(canvas);
bool willClip = properties.getOutline().willClip();
if (willClip) {
canvas->save();
clipOutline(properties.getOutline(), canvas, nullptr);
}
// draw the collected projected nodes
for (auto& projectedChild : newList) {
canvas->setMatrix(projectedChild.matrix);
projectedChild.node->drawContent(canvas);
}
if (willClip) {
canvas->restore();
}
} else {
if (properties.getProjectBackwards() && mProjectedChildrenTarget) {
// We are supposed to project this node, so add it to the list and do not actually draw
// yet. It will be drawn by its projection receiver.
mProjectedChildrenTarget->push_back({ this, canvas->getTotalMatrix() });
return;
}
for (auto& child : displayList->mChildNodes) {
// storing these values in the nodes themselves is a bit ugly; they should "really" be
// function parameters, but we have to go through the preexisting draw() method and
// therefore cannot add additional parameters to it
child.mProjectedChildrenTarget = mNextProjectedChildrenTarget;
child.mNextProjectedChildrenTarget = mNextProjectedChildrenTarget;
}
this->drawContent(canvas);
}
mProjectedChildrenTarget = nullptr;
mNextProjectedChildrenTarget = nullptr;
displayList->mProjectedOutline = nullptr;
}
static bool layerNeedsPaint(const LayerProperties& properties,
@@ -148,6 +153,10 @@ void RenderNodeDrawable::drawContent(SkCanvas* canvas) const {
if (mComposeLayer) {
setViewProperties(properties, canvas, &alphaMultiplier);
}
SkiaDisplayList* displayList = (SkiaDisplayList*)mRenderNode->getDisplayList();
if (displayList->containsProjectionReceiver()) {
displayList->mProjectedReceiverParentMatrix = canvas->getTotalMatrix();
}
//TODO should we let the bound of the drawable do this for us?
const SkRect bounds = SkRect::MakeWH(properties.getWidth(), properties.getHeight());

View File

@@ -29,24 +29,14 @@ class RenderProperties;
namespace skiapipeline {
class SkiaDisplayList;
/**
* This drawable wraps a RenderNode and enables it to be recorded into a list
* of Skia drawing commands.
*/
class RenderNodeDrawable : public SkDrawable {
public:
/**
* This struct contains a pointer to a node that is to be
* projected into the drawing order of its closest ancestor
* (excluding its parent) that is marked as a projection
* receiver. The matrix is used to ensure that the node is
* drawn with same matrix as it would have prior to projection.
*/
struct ProjectedChild {
const RenderNodeDrawable* node;
const SkMatrix matrix;
};
/**
* Creates a new RenderNodeDrawable backed by a render node.
*
@@ -86,6 +76,14 @@ public:
*/
const SkMatrix& getRecordedMatrix() const { return mRecordedTransform; }
/**
* Sets a pointer to a display list of the parent render node. The display list is used when
* drawing backward projected nodes, when this node is a projection receiver.
*/
void setProjectedDisplayList(SkiaDisplayList* projectedDisplayList) {
mProjectedDisplayList = projectedDisplayList;
}
protected:
/*
* Return the (conservative) bounds of what the drawable will draw.
@@ -107,6 +105,16 @@ private:
*/
sp<RenderNode> mRenderNode;
/**
* Walks recursively the display list and draws the content of backward projected nodes.
*
* @param canvas used to draw the backward projected nodes
* @param displayList is a display list that contains a projection receiver
* @param nestLevel should be always 0. Used to track how far we are from the receiver.
*/
void drawBackwardsProjectedNodes(SkCanvas* canvas, const SkiaDisplayList& displayList,
int nestLevel = 0);
/**
* Applies the rendering properties of a view onto a SkCanvas.
*/
@@ -126,19 +134,6 @@ private:
*/
const bool mComposeLayer;
/**
* List to which we will add any projected children we encounter while walking our descendents.
* This pointer is valid only while the node (including its children) is actively being drawn.
*/
std::vector<ProjectedChild>* mProjectedChildrenTarget = nullptr;
/**
* The value to which we should set our children's mProjectedChildrenTarget. We use two pointers
* (mProjectedChildrenTarget and mNextProjectedChildrenTarget) because we need to skip over our
* parent when looking for a projection receiver.
*/
std::vector<ProjectedChild>* mNextProjectedChildrenTarget = nullptr;
/*
* True if the render node is in a reordering section
*/
@@ -148,6 +143,11 @@ private:
* Draw the content into a canvas, depending on the render node layer type and mComposeLayer.
*/
void drawContent(SkCanvas* canvas) const;
/*
* display list that is searched for any render nodes with getProjectBackwards==true
*/
SkiaDisplayList* mProjectedDisplayList = nullptr;
};
}; // namespace skiapipeline

View File

@@ -64,16 +64,31 @@ bool SkiaDisplayList::prepareListAndChildren(TreeInfo& info, bool functorsNeedLa
info.canvasContext.unpinImages();
}
bool hasBackwardProjectedNodesHere = false;
bool hasBackwardProjectedNodesSubtree= false;
for (auto& child : mChildNodes) {
hasBackwardProjectedNodesHere |= child.getNodeProperties().getProjectBackwards();
RenderNode* childNode = child.getRenderNode();
Matrix4 mat4(child.getRecordedMatrix());
info.damageAccumulator->pushTransform(&mat4);
// TODO: a layer is needed if the canvas is rotated or has a non-rect clip
bool childFunctorsNeedLayer = functorsNeedLayer;
childFn(childNode, info, childFunctorsNeedLayer);
info.hasBackwardProjectedNodes = false;
childFn(childNode, info, functorsNeedLayer);
hasBackwardProjectedNodesSubtree |= info.hasBackwardProjectedNodes;
info.damageAccumulator->popTransform();
}
//The purpose of next block of code is to reset projected display list if there are no
//backward projected nodes. This speeds up drawing, by avoiding an extra walk of the tree
if (mProjectionReceiver) {
mProjectionReceiver->setProjectedDisplayList(hasBackwardProjectedNodesSubtree ? this : nullptr);
info.hasBackwardProjectedNodes = hasBackwardProjectedNodesHere;
} else {
info.hasBackwardProjectedNodes = hasBackwardProjectedNodesSubtree
|| hasBackwardProjectedNodesHere;
}
bool isDirty = false;
for (auto& vectorDrawable : mVectorDrawables) {
// If any vector drawable in the display list needs update, damage the node.
@@ -86,7 +101,7 @@ bool SkiaDisplayList::prepareListAndChildren(TreeInfo& info, bool functorsNeedLa
}
void SkiaDisplayList::reset(SkRect bounds) {
mIsProjectionReceiver = false;
mProjectionReceiver = nullptr;
mDrawable->reset(bounds);

View File

@@ -26,6 +26,9 @@
namespace android {
namespace uirenderer {
class Outline;
namespace skiapipeline {
/**
@@ -118,6 +121,11 @@ public:
*/
void updateChildren(std::function<void(RenderNode*)> updateFn) override;
/**
* Returns true if there is a child render node that is a projection receiver.
*/
inline bool containsProjectionReceiver() const { return mProjectionReceiver; }
/**
* We use std::deque here because (1) we need to iterate through these
* elements and (2) mDrawable holds pointers to the elements, so they cannot
@@ -129,7 +137,22 @@ public:
std::vector<VectorDrawableRoot*> mVectorDrawables;
sk_sp<SkLiteDL> mDrawable;
bool mIsProjectionReceiver = false;
//mProjectionReceiver points to a child node (stored in mChildNodes) that is as a projection
//receiver. It is set at record time and used at both prepare and draw tree traversals to
//make sure backward projected nodes are found and drawn immediately after mProjectionReceiver.
RenderNodeDrawable* mProjectionReceiver = nullptr;
//mProjectedOutline is valid only when render node tree is traversed during the draw pass.
//Render nodes that have a child receiver node, will store a pointer to their outline in
//mProjectedOutline. Child receiver node will apply the clip before any backward projected
//node is drawn.
const Outline* mProjectedOutline = nullptr;
//mProjectedReceiverParentMatrix is valid when render node tree is traversed during the draw
//pass. Render nodes that have a child receiver node, will store their matrix in
//mProjectedReceiverParentMatrix. Child receiver node will set the matrix and then clip with the
//outline of their parent.
SkMatrix mProjectedReceiverParentMatrix;
};
}; // namespace skiapipeline

View File

@@ -108,11 +108,12 @@ void SkiaRecordingCanvas::drawRenderNode(uirenderer::RenderNode* renderNode) {
// record the child node
mDisplayList->mChildNodes.emplace_back(renderNode, asSkCanvas(), true, mCurrentBarrier);
drawDrawable(&mDisplayList->mChildNodes.back());
auto& renderNodeDrawable = mDisplayList->mChildNodes.back();
drawDrawable(&renderNodeDrawable);
// use staging property, since recording on UI thread
if (renderNode->stagingProperties().isProjectionReceiver()) {
mDisplayList->mIsProjectionReceiver = true;
mDisplayList->mProjectionReceiver = &renderNodeDrawable;
// set projectionReceiveIndex so that RenderNode.hasProjectionReceiver returns true
mDisplayList->projectionReceiveIndex = mDisplayList->mChildNodes.size() - 1;
}

View File

@@ -1487,6 +1487,8 @@ RENDERTHREAD_TEST(FrameBuilder, buildLayer) {
*layerHandle = nullptr;
}
namespace {
static void drawOrderedRect(Canvas* canvas, uint8_t expectedDrawOrder) {
SkPaint paint;
// order put in blue channel, transparent so overlapped content doesn't get rejected
@@ -1502,15 +1504,30 @@ static void drawOrderedNode(Canvas* canvas, uint8_t expectedDrawOrder, float z)
node->setPropertyFieldsDirty(RenderNode::TRANSLATION_Z);
canvas->drawRenderNode(node.get()); // canvas takes reference/sole ownership
}
RENDERTHREAD_TEST(FrameBuilder, zReorder) {
class ZReorderTestRenderer : public TestRendererBase {
public:
void onRectOp(const RectOp& op, const BakedOpState& state) override {
int expectedOrder = SkColorGetB(op.paint->getColor()); // extract order from blue channel
EXPECT_EQ(expectedOrder, mIndex++) << "An op was drawn out of order";
}
};
static void drawOrderedNode(Canvas* canvas, uint8_t expectedDrawOrder,
std::function<void(RenderProperties& props, RecordingCanvas& canvas)> setup) {
auto node = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
[expectedDrawOrder, setup](RenderProperties& props, RecordingCanvas& canvas) {
drawOrderedRect(&canvas, expectedDrawOrder);
if (setup) {
setup(props, canvas);
}
});
canvas->drawRenderNode(node.get()); // canvas takes reference/sole ownership
}
class ZReorderTestRenderer : public TestRendererBase {
public:
void onRectOp(const RectOp& op, const BakedOpState& state) override {
int expectedOrder = SkColorGetB(op.paint->getColor()); // extract order from blue channel
EXPECT_EQ(expectedOrder, mIndex++) << "An op was drawn out of order";
}
};
} // end anonymous namespace
RENDERTHREAD_TEST(FrameBuilder, zReorder) {
auto parent = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
[](RenderProperties& props, RecordingCanvas& canvas) {
drawOrderedNode(&canvas, 0, 10.0f); // in reorder=false at this point, so played inorder
@@ -2238,5 +2255,349 @@ RENDERTHREAD_TEST(FrameBuilder, clip_replace) {
EXPECT_EQ(1, renderer.getIndex());
}
TEST(FrameBuilder, projectionReorderProjectedInMiddle) {
/* R is backward projected on B
A
/ \
B C
|
R
*/
auto nodeA = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
[](RenderProperties& props, RecordingCanvas& canvas) {
drawOrderedNode(&canvas, 0, [](RenderProperties& props, RecordingCanvas& canvas) {
props.setProjectionReceiver(true);
} ); //nodeB
drawOrderedNode(&canvas, 2, [](RenderProperties& props, RecordingCanvas& canvas) {
drawOrderedNode(&canvas, 1, [](RenderProperties& props, RecordingCanvas& canvas) {
props.setProjectBackwards(true);
props.setClipToBounds(false);
} ); //nodeR
} ); //nodeC
}); //nodeA
FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
sLightGeometry, Caches::getInstance());
frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
ZReorderTestRenderer renderer;
frameBuilder.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(3, renderer.getIndex());
}
TEST(FrameBuilder, projectionReorderProjectLast) {
/* R is backward projected on E
A
/ | \
/ | \
B C E
|
R
*/
auto nodeA = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
[](RenderProperties& props, RecordingCanvas& canvas) {
drawOrderedNode(&canvas, 0, nullptr); //nodeB
drawOrderedNode(&canvas, 1, [](RenderProperties& props, RecordingCanvas& canvas) {
drawOrderedNode(&canvas, 3, [](RenderProperties& props, RecordingCanvas& canvas) { //drawn as 2
props.setProjectBackwards(true);
props.setClipToBounds(false);
} ); //nodeR
} ); //nodeC
drawOrderedNode(&canvas, 2, [](RenderProperties& props, RecordingCanvas& canvas) { //drawn as 3
props.setProjectionReceiver(true);
} ); //nodeE
}); //nodeA
FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
sLightGeometry, Caches::getInstance());
frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
ZReorderTestRenderer renderer;
frameBuilder.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(4, renderer.getIndex());
}
TEST(FrameBuilder, projectionReorderNoReceivable) {
/* R is backward projected without receiver
A
/ \
B C
|
R
*/
auto nodeA = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
[](RenderProperties& props, RecordingCanvas& canvas) {
drawOrderedNode(&canvas, 0, nullptr); //nodeB
drawOrderedNode(&canvas, 1, [](RenderProperties& props, RecordingCanvas& canvas) {
drawOrderedNode(&canvas, 255, [](RenderProperties& props, RecordingCanvas& canvas) {
//not having a projection receiver is an undefined behavior
props.setProjectBackwards(true);
props.setClipToBounds(false);
} ); //nodeR
} ); //nodeC
}); //nodeA
FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
sLightGeometry, Caches::getInstance());
frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
ZReorderTestRenderer renderer;
frameBuilder.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(2, renderer.getIndex());
}
TEST(FrameBuilder, projectionReorderParentReceivable) {
/* R is backward projected on C
A
/ \
B C
|
R
*/
auto nodeA = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
[](RenderProperties& props, RecordingCanvas& canvas) {
drawOrderedNode(&canvas, 0, nullptr); //nodeB
drawOrderedNode(&canvas, 1, [](RenderProperties& props, RecordingCanvas& canvas) {
props.setProjectionReceiver(true);
drawOrderedNode(&canvas, 2, [](RenderProperties& props, RecordingCanvas& canvas) {
props.setProjectBackwards(true);
props.setClipToBounds(false);
} ); //nodeR
} ); //nodeC
}); //nodeA
FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
sLightGeometry, Caches::getInstance());
frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
ZReorderTestRenderer renderer;
frameBuilder.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(3, renderer.getIndex());
}
TEST(FrameBuilder, projectionReorderSameNodeReceivable) {
auto nodeA = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
[](RenderProperties& props, RecordingCanvas& canvas) {
drawOrderedNode(&canvas, 0, nullptr); //nodeB
drawOrderedNode(&canvas, 1, [](RenderProperties& props, RecordingCanvas& canvas) {
drawOrderedNode(&canvas, 255, [](RenderProperties& props, RecordingCanvas& canvas) {
//having a node that is projected on itself is an undefined/unexpected behavior
props.setProjectionReceiver(true);
props.setProjectBackwards(true);
props.setClipToBounds(false);
} ); //nodeR
} ); //nodeC
}); //nodeA
FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
sLightGeometry, Caches::getInstance());
frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
ZReorderTestRenderer renderer;
frameBuilder.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(2, renderer.getIndex());
}
TEST(FrameBuilder, projectionReorderProjectedSibling) {
//TODO: this test together with the next "projectionReorderProjectedSibling2" likely expose a
//bug in HWUI. First test draws R, while the second test does not draw R for a nearly identical
//tree setup. The correct behaviour is to not draw R, because the receiver cannot be a sibling
/* R is backward projected on B. R is not expected to be drawn (see Sibling2 outcome below),
but for some reason it is drawn.
A
/|\
/ | \
B C R
*/
auto nodeA = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
[](RenderProperties& props, RecordingCanvas& canvas) {
drawOrderedNode(&canvas, 0, [](RenderProperties& props, RecordingCanvas& canvas) {
props.setProjectionReceiver(true);
} ); //nodeB
drawOrderedNode(&canvas, 2, [](RenderProperties& props, RecordingCanvas& canvas) {
} ); //nodeC
drawOrderedNode(&canvas, 1, [](RenderProperties& props, RecordingCanvas& canvas) {
props.setProjectBackwards(true);
props.setClipToBounds(false);
} ); //nodeR
}); //nodeA
FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
sLightGeometry, Caches::getInstance());
frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
ZReorderTestRenderer renderer;
frameBuilder.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(3, renderer.getIndex());
}
TEST(FrameBuilder, projectionReorderProjectedSibling2) {
/* R is set to project on B, but R is not drawn because projecting on a sibling is not allowed.
A
|
G
/|\
/ | \
B C R
*/
auto nodeA = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
[](RenderProperties& props, RecordingCanvas& canvas) {
drawOrderedNode(&canvas, 0, [](RenderProperties& props, RecordingCanvas& canvas) { //G
drawOrderedNode(&canvas, 1, [](RenderProperties& props, RecordingCanvas& canvas) { //B
props.setProjectionReceiver(true);
} ); //nodeB
drawOrderedNode(&canvas, 2, [](RenderProperties& props, RecordingCanvas& canvas) { //C
} ); //nodeC
drawOrderedNode(&canvas, 255, [](RenderProperties& props, RecordingCanvas& canvas) { //R
props.setProjectBackwards(true);
props.setClipToBounds(false);
} ); //nodeR
} ); //nodeG
}); //nodeA
FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
sLightGeometry, Caches::getInstance());
frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
ZReorderTestRenderer renderer;
frameBuilder.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(3, renderer.getIndex());
}
TEST(FrameBuilder, projectionReorderGrandparentReceivable) {
/* R is backward projected on B
A
|
B
|
C
|
R
*/
auto nodeA = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
[](RenderProperties& props, RecordingCanvas& canvas) {
drawOrderedNode(&canvas, 0, [](RenderProperties& props, RecordingCanvas& canvas) {
props.setProjectionReceiver(true);
drawOrderedNode(&canvas, 1, [](RenderProperties& props, RecordingCanvas& canvas) {
drawOrderedNode(&canvas, 2, [](RenderProperties& props, RecordingCanvas& canvas) {
props.setProjectBackwards(true);
props.setClipToBounds(false);
} ); //nodeR
} ); //nodeC
} ); //nodeB
}); //nodeA
FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
sLightGeometry, Caches::getInstance());
frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
ZReorderTestRenderer renderer;
frameBuilder.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(3, renderer.getIndex());
}
TEST(FrameBuilder, projectionReorderTwoReceivables) {
/* B and G are receivables, R is backward projected
A
/ \
B C
/ \
G R
*/
auto nodeA = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
[](RenderProperties& props, RecordingCanvas& canvas) {
drawOrderedNode(&canvas, 0, [](RenderProperties& props, RecordingCanvas& canvas) { //B
props.setProjectionReceiver(true);
} ); //nodeB
drawOrderedNode(&canvas, 2, [](RenderProperties& props, RecordingCanvas& canvas) { //C
drawOrderedNode(&canvas, 3, [](RenderProperties& props, RecordingCanvas& canvas) { //G
props.setProjectionReceiver(true);
} ); //nodeG
drawOrderedNode(&canvas, 1, [](RenderProperties& props, RecordingCanvas& canvas) { //R
props.setProjectBackwards(true);
props.setClipToBounds(false);
} ); //nodeR
} ); //nodeC
}); //nodeA
FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
sLightGeometry, Caches::getInstance());
frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
ZReorderTestRenderer renderer;
frameBuilder.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(4, renderer.getIndex());
}
TEST(FrameBuilder, projectionReorderTwoReceivablesLikelyScenario) {
/* B and G are receivables, G is backward projected
A
/ \
B C
/ \
G R
*/
auto nodeA = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
[](RenderProperties& props, RecordingCanvas& canvas) {
drawOrderedNode(&canvas, 0, [](RenderProperties& props, RecordingCanvas& canvas) { //B
props.setProjectionReceiver(true);
} ); //nodeB
drawOrderedNode(&canvas, 2, [](RenderProperties& props, RecordingCanvas& canvas) { //C
drawOrderedNode(&canvas, 1, [](RenderProperties& props, RecordingCanvas& canvas) { //G
props.setProjectionReceiver(true);
props.setProjectBackwards(true);
props.setClipToBounds(false);
} ); //nodeG
drawOrderedNode(&canvas, 3, [](RenderProperties& props, RecordingCanvas& canvas) { //R
} ); //nodeR
} ); //nodeC
}); //nodeA
FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
sLightGeometry, Caches::getInstance());
frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
ZReorderTestRenderer renderer;
frameBuilder.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(4, renderer.getIndex());
}
TEST(FrameBuilder, projectionReorderTwoReceivablesDeeper) {
/* B and G are receivables, R is backward projected
A
/ \
B C
/ \
G D
|
R
*/
auto nodeA = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
[](RenderProperties& props, RecordingCanvas& canvas) {
drawOrderedNode(&canvas, 0, [](RenderProperties& props, RecordingCanvas& canvas) { //B
props.setProjectionReceiver(true);
} ); //nodeB
drawOrderedNode(&canvas, 1, [](RenderProperties& props, RecordingCanvas& canvas) { //C
drawOrderedNode(&canvas, 2, [](RenderProperties& props, RecordingCanvas& canvas) { //G
props.setProjectionReceiver(true);
} ); //nodeG
drawOrderedNode(&canvas, 4, [](RenderProperties& props, RecordingCanvas& canvas) { //D
drawOrderedNode(&canvas, 3, [](RenderProperties& props, RecordingCanvas& canvas) { //R
props.setProjectBackwards(true);
props.setClipToBounds(false);
} ); //nodeR
} ); //nodeD
} ); //nodeC
}); //nodeA
FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
sLightGeometry, Caches::getInstance());
frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
ZReorderTestRenderer renderer;
frameBuilder.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(5, renderer.getIndex());
}
} // namespace uirenderer
} // namespace android

View File

@@ -21,11 +21,14 @@
#include "DamageAccumulator.h"
#include "IContextFactory.h"
#include "pipeline/skia/SkiaDisplayList.h"
#include "pipeline/skia/SkiaPipeline.h"
#include "pipeline/skia/SkiaRecordingCanvas.h"
#include "renderthread/CanvasContext.h"
#include "tests/common/TestUtils.h"
#include "SkiaCanvas.h"
#include <SkSurface_Base.h>
#include <SkLiteRecorder.h>
#include <SkClipStack.h>
#include <string.h>
@@ -51,6 +54,8 @@ TEST(RenderNodeDrawable, create) {
ASSERT_EQ(drawable.getRecordedMatrix(), canvas.getTotalMatrix());
}
namespace {
static void drawOrderedRect(Canvas* canvas, uint8_t expectedDrawOrder) {
SkPaint paint;
// order put in blue channel, transparent so overlapped content doesn't get rejected
@@ -67,18 +72,33 @@ static void drawOrderedNode(Canvas* canvas, uint8_t expectedDrawOrder, float z)
canvas->drawRenderNode(node.get()); // canvas takes reference/sole ownership
}
TEST(RenderNodeDrawable, zReorder) {
class ZReorderCanvas : public SkCanvas {
public:
ZReorderCanvas(int width, int height) : SkCanvas(width, height) {}
void onDrawRect(const SkRect& rect, const SkPaint& paint) override {
int expectedOrder = SkColorGetB(paint.getColor()); // extract order from blue channel
EXPECT_EQ(expectedOrder, mIndex++) << "An op was drawn out of order";
static void drawOrderedNode(Canvas* canvas, uint8_t expectedDrawOrder,
std::function<void(RenderProperties& props, SkiaRecordingCanvas& canvas)> setup) {
auto node = TestUtils::createSkiaNode(0, 0, 100, 100,
[expectedDrawOrder, setup](RenderProperties& props, SkiaRecordingCanvas& canvas) {
drawOrderedRect(&canvas, expectedDrawOrder);
if (setup) {
setup(props, canvas);
}
int getIndex() { return mIndex; }
protected:
int mIndex = 0;
};
});
canvas->drawRenderNode(node.get()); // canvas takes reference/sole ownership
}
class ZReorderCanvas : public SkCanvas {
public:
ZReorderCanvas(int width, int height) : SkCanvas(width, height) {}
void onDrawRect(const SkRect& rect, const SkPaint& paint) override {
int expectedOrder = SkColorGetB(paint.getColor()); // extract order from blue channel
EXPECT_EQ(expectedOrder, mIndex++) << "An op was drawn out of order";
}
int getIndex() { return mIndex; }
protected:
int mIndex = 0;
};
} // end anonymous namespace
TEST(RenderNodeDrawable, zReorder) {
auto parent = TestUtils::createSkiaNode(0, 0, 100, 100,
[](RenderProperties& props, SkiaRecordingCanvas& canvas) {
@@ -136,42 +156,622 @@ TEST(RenderNodeDrawable, composeOnLayer)
rootNode->setLayerSurface(sk_sp<SkSurface>());
}
//TODO: refactor to cover test cases from FrameBuilderTests_projectionReorder
//validate with bounds and projection path mask.
//TODO: research if we could hook in and mock/validate different aspects of the drawing,
//instead of validating pixels
TEST(RenderNodeDrawable, projectDraw) {
auto surface = SkSurface::MakeRasterN32Premul(1, 1);
SkCanvas& canvas = *surface->getCanvas();
canvas.drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
namespace {
class ContextFactory : public IContextFactory {
public:
virtual AnimationContext* createAnimationContext(renderthread::TimeLord& clock) override {
return new AnimationContext(clock);
}
};
auto redNode = TestUtils::createSkiaNode(0, 0, 1, 1,
[](RenderProperties& props, SkiaRecordingCanvas& redCanvas) {
redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver);
}, "redNode");
auto greenNodeWithRedChild = TestUtils::createSkiaNode(0, 0, 1, 1,
[&](RenderProperties& props, SkiaRecordingCanvas& greenCanvasWithRedChild) {
greenCanvasWithRedChild.drawRenderNode(redNode.get());
greenCanvasWithRedChild.drawColor(SK_ColorGREEN, SkBlendMode::kSrcOver);
}, "greenNodeWithRedChild");
auto rootNode = TestUtils::createSkiaNode(0, 0, 1, 1,
[&](RenderProperties& props, SkiaRecordingCanvas& rootCanvas) {
rootCanvas.drawRenderNode(greenNodeWithRedChild.get());
}, "rootNode");
SkiaDisplayList* rootDisplayList = static_cast<SkiaDisplayList*>(
(const_cast<DisplayList*>(rootNode->getDisplayList())));
RenderNodeDrawable rootDrawable(rootNode.get(), &canvas, false);
canvas.drawDrawable(&rootDrawable);
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorGREEN);
//project redNode on rootNode, which will change the test outcome,
//because redNode will draw after greenNodeWithRedChild
rootDisplayList->mIsProjectionReceiver = true;
redNode->animatorProperties().setProjectBackwards(true);
canvas.drawDrawable(&rootDrawable);
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorRED);
inline SkRect getBounds(const SkCanvas* canvas) {
SkClipStack::BoundsType boundType;
SkRect clipBounds;
canvas->getClipStack()->getBounds(&clipBounds, &boundType);
return clipBounds;
}
inline SkRect getLocalBounds(const SkCanvas* canvas) {
SkMatrix invertedTotalMatrix;
EXPECT_TRUE(canvas->getTotalMatrix().invert(&invertedTotalMatrix));
SkRect outlineInDeviceCoord = getBounds(canvas);
SkRect outlineInLocalCoord;
invertedTotalMatrix.mapRect(&outlineInLocalCoord, outlineInDeviceCoord);
return outlineInLocalCoord;
}
} // end anonymous namespace
RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorder) {
static const int SCROLL_X = 5;
static const int SCROLL_Y = 10;
class ProjectionTestCanvas : public SkCanvas {
public:
ProjectionTestCanvas(int width, int height) : SkCanvas(width, height) {}
void onDrawRect(const SkRect& rect, const SkPaint& paint) override {
const int index = mIndex++;
SkMatrix expectedMatrix;;
switch (index) {
case 0: //this is node "B"
EXPECT_EQ(SkRect::MakeWH(100, 100), rect);
EXPECT_EQ(SK_ColorWHITE, paint.getColor());
expectedMatrix.reset();
EXPECT_EQ(SkRect::MakeLTRB(0, 0, 100, 100), getBounds(this));
break;
case 1: //this is node "P"
EXPECT_EQ(SkRect::MakeLTRB(-10, -10, 60, 60), rect);
EXPECT_EQ(SK_ColorDKGRAY, paint.getColor());
expectedMatrix.setTranslate(50 - SCROLL_X, 50 - SCROLL_Y);
EXPECT_EQ(SkRect::MakeLTRB(-35, -30, 45, 50), getLocalBounds(this));
break;
case 2: //this is node "C"
EXPECT_EQ(SkRect::MakeWH(100, 50), rect);
EXPECT_EQ(SK_ColorBLUE, paint.getColor());
expectedMatrix.setTranslate(-SCROLL_X, 50 - SCROLL_Y);
EXPECT_EQ(SkRect::MakeLTRB(0, 40, 95, 90), getBounds(this));
break;
default:
ADD_FAILURE();
}
EXPECT_EQ(expectedMatrix, getTotalMatrix());
}
int getIndex() { return mIndex; }
protected:
int mIndex = 0;
};
/**
* Construct a tree of nodes, where the root (A) has a receiver background (B), and a child (C)
* with a projecting child (P) of its own. P would normally draw between B and C's "background"
* draw, but because it is projected backwards, it's drawn in between B and C.
*
* The parent is scrolled by SCROLL_X/SCROLL_Y, but this does not affect the background
* (which isn't affected by scroll).
*/
auto receiverBackground = TestUtils::createSkiaNode(0, 0, 100, 100,
[](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
properties.setProjectionReceiver(true);
// scroll doesn't apply to background, so undone via translationX/Y
// NOTE: translationX/Y only! no other transform properties may be set for a proj receiver!
properties.setTranslationX(SCROLL_X);
properties.setTranslationY(SCROLL_Y);
SkPaint paint;
paint.setColor(SK_ColorWHITE);
canvas.drawRect(0, 0, 100, 100, paint);
}, "B");
auto projectingRipple = TestUtils::createSkiaNode(50, 0, 100, 50,
[](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
properties.setProjectBackwards(true);
properties.setClipToBounds(false);
SkPaint paint;
paint.setColor(SK_ColorDKGRAY);
canvas.drawRect(-10, -10, 60, 60, paint);
}, "P");
auto child = TestUtils::createSkiaNode(0, 50, 100, 100,
[&projectingRipple](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
SkPaint paint;
paint.setColor(SK_ColorBLUE);
canvas.drawRect(0, 0, 100, 50, paint);
canvas.drawRenderNode(projectingRipple.get());
}, "C");
auto parent = TestUtils::createSkiaNode(0, 0, 100, 100,
[&receiverBackground, &child](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
// Set a rect outline for the projecting ripple to be masked against.
properties.mutableOutline().setRoundRect(10, 10, 90, 90, 5, 1.0f);
canvas.save(SaveFlags::MatrixClip);
canvas.translate(-SCROLL_X, -SCROLL_Y); // Apply scroll (note: bg undoes this internally)
canvas.drawRenderNode(receiverBackground.get());
canvas.drawRenderNode(child.get());
canvas.restore();
}, "A");
ContextFactory contextFactory;
std::unique_ptr<CanvasContext> canvasContext(CanvasContext::create(
renderThread, false, parent.get(), &contextFactory));
TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
DamageAccumulator damageAccumulator;
info.damageAccumulator = &damageAccumulator;
info.observer = nullptr;
parent->prepareTree(info);
//parent(A) -> (receiverBackground, child)
//child(C) -> (rect[0, 0, 100, 50], projectingRipple)
//projectingRipple(P) -> (rect[-10, -10, 60, 60]) -> projects backwards
//receiverBackground(B) -> (rect[0, 0, 100, 100]) -> projection receiver
//create a canvas not backed by any device/pixels, but with dimensions to avoid quick rejection
ProjectionTestCanvas canvas(100, 100);
RenderNodeDrawable drawable(parent.get(), &canvas, true);
canvas.drawDrawable(&drawable);
EXPECT_EQ(3, canvas.getIndex());
}
RENDERTHREAD_TEST(RenderNodeDrawable, projectionHwLayer) {
/* R is backward projected on B and C is a layer.
A
/ \
B C
|
R
*/
static const int SCROLL_X = 5;
static const int SCROLL_Y = 10;
static const int CANVAS_WIDTH = 400;
static const int CANVAS_HEIGHT = 400;
static const int LAYER_WIDTH = 200;
static const int LAYER_HEIGHT = 200;
class ProjectionTestCanvas : public SkCanvas {
public:
ProjectionTestCanvas() : SkCanvas(CANVAS_WIDTH, CANVAS_HEIGHT) {}
void onDrawArc(const SkRect&, SkScalar startAngle, SkScalar sweepAngle, bool useCenter,
const SkPaint&) override {
EXPECT_EQ(0, mIndex++); //part of painting the layer
EXPECT_EQ(SkRect::MakeLTRB(0, 0, LAYER_WIDTH, LAYER_HEIGHT), getBounds(this));
}
void onDrawRect(const SkRect& rect, const SkPaint& paint) override {
EXPECT_EQ(1, mIndex++);
EXPECT_EQ(SkRect::MakeLTRB(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT), getBounds(this));
}
void onDrawOval(const SkRect&, const SkPaint&) override {
EXPECT_EQ(2, mIndex++);
SkMatrix expectedMatrix;
expectedMatrix.setTranslate(100 - SCROLL_X, 100 - SCROLL_Y);
EXPECT_EQ(expectedMatrix, getTotalMatrix());
EXPECT_EQ(SkRect::MakeLTRB(-85, -80, 295, 300), getLocalBounds(this));
}
int mIndex = 0;
};
class ProjectionLayer : public SkSurface_Base {
public:
ProjectionLayer(ProjectionTestCanvas *canvas)
: SkSurface_Base(SkImageInfo::MakeN32Premul(LAYER_WIDTH, LAYER_HEIGHT), nullptr)
, mCanvas(canvas) {
}
void onDraw(SkCanvas*, SkScalar x, SkScalar y, const SkPaint*) override {
EXPECT_EQ(3, mCanvas->mIndex++);
EXPECT_EQ(SkRect::MakeLTRB(100 - SCROLL_X, 100 - SCROLL_Y, 300 - SCROLL_X,
300 - SCROLL_Y), getBounds(mCanvas));
}
SkCanvas* onNewCanvas() override {
mCanvas->ref();
return mCanvas;
}
sk_sp<SkSurface> onNewSurface(const SkImageInfo&) override {
return sk_sp<SkSurface>();
}
sk_sp<SkImage> onNewImageSnapshot(SkBudgeted, SkCopyPixelsMode) override {
return sk_sp<SkImage>();
}
void onCopyOnWrite(ContentChangeMode) override {}
ProjectionTestCanvas* mCanvas;
};
auto receiverBackground = TestUtils::createSkiaNode(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT,
[](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
properties.setProjectionReceiver(true);
// scroll doesn't apply to background, so undone via translationX/Y
// NOTE: translationX/Y only! no other transform properties may be set for a proj receiver!
properties.setTranslationX(SCROLL_X);
properties.setTranslationY(SCROLL_Y);
canvas.drawRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT, SkPaint());
}, "B"); //B
auto projectingRipple = TestUtils::createSkiaNode(0, 0, LAYER_WIDTH, LAYER_HEIGHT,
[](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
properties.setProjectBackwards(true);
properties.setClipToBounds(false);
canvas.drawOval(100, 100, 300, 300, SkPaint()); // drawn mostly out of layer bounds
}, "R"); //R
auto child = TestUtils::createSkiaNode(100, 100, 300, 300,
[&projectingRipple](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
canvas.drawRenderNode(projectingRipple.get());
canvas.drawArc(0, 0, LAYER_WIDTH, LAYER_HEIGHT, 0.0f, 280.0f, true, SkPaint());
}, "C"); //C
auto parent = TestUtils::createSkiaNode(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT,
[&receiverBackground, &child](RenderProperties& properties,
SkiaRecordingCanvas& canvas) {
// Set a rect outline for the projecting ripple to be masked against.
properties.mutableOutline().setRoundRect(10, 10, 390, 390, 0, 1.0f);
canvas.translate(-SCROLL_X, -SCROLL_Y); // Apply scroll (note: bg undoes this internally)
canvas.drawRenderNode(receiverBackground.get());
canvas.drawRenderNode(child.get());
}, "A"); //A
//prepareTree is required to find, which receivers have backward projected nodes
ContextFactory contextFactory;
std::unique_ptr<CanvasContext> canvasContext(CanvasContext::create(
renderThread, false, parent.get(), &contextFactory));
TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
DamageAccumulator damageAccumulator;
info.damageAccumulator = &damageAccumulator;
info.observer = nullptr;
parent->prepareTree(info);
sk_sp<ProjectionTestCanvas> canvas(new ProjectionTestCanvas());
//set a layer after prepareTree to avoid layer logic there
child->animatorProperties().mutateLayerProperties().setType(LayerType::RenderLayer);
sk_sp<SkSurface> surfaceLayer1(new ProjectionLayer(canvas.get()));
child->setLayerSurface(surfaceLayer1);
Matrix4 windowTransform;
windowTransform.loadTranslate(100, 100, 0);
child->getSkiaLayer()->inverseTransformInWindow.loadInverse(windowTransform);
LayerUpdateQueue layerUpdateQueue;
layerUpdateQueue.enqueueLayerWithDamage(child.get(),
android::uirenderer::Rect(LAYER_WIDTH, LAYER_HEIGHT));
SkiaPipeline::renderLayersImpl(layerUpdateQueue, true);
EXPECT_EQ(1, canvas->mIndex); //assert index 0 is drawn on the layer
RenderNodeDrawable drawable(parent.get(), canvas.get(), true);
canvas->drawDrawable(&drawable);
EXPECT_EQ(4, canvas->mIndex);
// clean up layer pointer, so we can safely destruct RenderNode
child->setLayerSurface(nullptr);
}
RENDERTHREAD_TEST(RenderNodeDrawable, projectionChildScroll) {
/* R is backward projected on B.
A
/ \
B C
|
R
*/
static const int SCROLL_X = 500000;
static const int SCROLL_Y = 0;
static const int CANVAS_WIDTH = 400;
static const int CANVAS_HEIGHT = 400;
class ProjectionChildScrollTestCanvas : public SkCanvas {
public:
ProjectionChildScrollTestCanvas() : SkCanvas(CANVAS_WIDTH, CANVAS_HEIGHT) {}
void onDrawRect(const SkRect& rect, const SkPaint& paint) override {
EXPECT_EQ(0, mIndex++);
EXPECT_TRUE(getTotalMatrix().isIdentity());
}
void onDrawOval(const SkRect&, const SkPaint&) override {
EXPECT_EQ(1, mIndex++);
EXPECT_EQ(SkRect::MakeWH(CANVAS_WIDTH, CANVAS_HEIGHT), getBounds(this));
EXPECT_TRUE(getTotalMatrix().isIdentity());
}
int mIndex = 0;
};
auto receiverBackground = TestUtils::createSkiaNode(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT,
[](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
properties.setProjectionReceiver(true);
canvas.drawRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT, SkPaint());
}, "B"); //B
auto projectingRipple = TestUtils::createSkiaNode(0, 0, 200, 200,
[](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
// scroll doesn't apply to background, so undone via translationX/Y
// NOTE: translationX/Y only! no other transform properties may be set for a proj receiver!
properties.setTranslationX(SCROLL_X);
properties.setTranslationY(SCROLL_Y);
properties.setProjectBackwards(true);
properties.setClipToBounds(false);
canvas.drawOval(0, 0, 200, 200, SkPaint());
}, "R"); //R
auto child = TestUtils::createSkiaNode(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT,
[&projectingRipple](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
// Record time clip will be ignored by projectee
canvas.clipRect(100, 100, 300, 300, SkRegion::kIntersect_Op);
canvas.translate(-SCROLL_X, -SCROLL_Y); // Apply scroll (note: bg undoes this internally)
canvas.drawRenderNode(projectingRipple.get());
}, "C"); //C
auto parent = TestUtils::createSkiaNode(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT,
[&receiverBackground, &child](RenderProperties& properties,
SkiaRecordingCanvas& canvas) {
canvas.drawRenderNode(receiverBackground.get());
canvas.drawRenderNode(child.get());
}, "A"); //A
//prepareTree is required to find, which receivers have backward projected nodes
ContextFactory contextFactory;
std::unique_ptr<CanvasContext> canvasContext(CanvasContext::create(
renderThread, false, parent.get(), &contextFactory));
TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
DamageAccumulator damageAccumulator;
info.damageAccumulator = &damageAccumulator;
info.observer = nullptr;
parent->prepareTree(info);
sk_sp<ProjectionChildScrollTestCanvas> canvas(new ProjectionChildScrollTestCanvas());
RenderNodeDrawable drawable(parent.get(), canvas.get(), true);
canvas->drawDrawable(&drawable);
EXPECT_EQ(2, canvas->mIndex);
}
namespace {
static int drawNode(RenderThread& renderThread, const sp<RenderNode>& renderNode)
{
ContextFactory contextFactory;
std::unique_ptr<CanvasContext> canvasContext(CanvasContext::create(
renderThread, false, renderNode.get(), &contextFactory));
TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
DamageAccumulator damageAccumulator;
info.damageAccumulator = &damageAccumulator;
info.observer = nullptr;
renderNode->prepareTree(info);
//create a canvas not backed by any device/pixels, but with dimensions to avoid quick rejection
ZReorderCanvas canvas(100, 100);
RenderNodeDrawable drawable(renderNode.get(), &canvas, false);
canvas.drawDrawable(&drawable);
return canvas.getIndex();
}
}
RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderProjectedInMiddle) {
/* R is backward projected on B
A
/ \
B C
|
R
*/
auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
[](RenderProperties& props, SkiaRecordingCanvas& canvas) {
drawOrderedNode(&canvas, 0, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
props.setProjectionReceiver(true);
} ); //nodeB
drawOrderedNode(&canvas, 2, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
props.setProjectBackwards(true);
props.setClipToBounds(false);
} ); //nodeR
} ); //nodeC
}); //nodeA
EXPECT_EQ(3, drawNode(renderThread, nodeA));
}
RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderProjectLast) {
/* R is backward projected on E
A
/ | \
/ | \
B C E
|
R
*/
auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
[](RenderProperties& props, SkiaRecordingCanvas& canvas) {
drawOrderedNode(&canvas, 0, nullptr); //nodeB
drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
drawOrderedNode(&canvas, 3, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //drawn as 2
props.setProjectBackwards(true);
props.setClipToBounds(false);
} ); //nodeR
} ); //nodeC
drawOrderedNode(&canvas, 2, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //drawn as 3
props.setProjectionReceiver(true);
} ); //nodeE
}); //nodeA
EXPECT_EQ(4, drawNode(renderThread, nodeA));
}
RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderNoReceivable) {
/* R is backward projected without receiver
A
/ \
B C
|
R
*/
auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
[](RenderProperties& props, SkiaRecordingCanvas& canvas) {
drawOrderedNode(&canvas, 0, nullptr); //nodeB
drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
drawOrderedNode(&canvas, 255, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
//not having a projection receiver is an undefined behavior
props.setProjectBackwards(true);
props.setClipToBounds(false);
} ); //nodeR
} ); //nodeC
}); //nodeA
EXPECT_EQ(2, drawNode(renderThread, nodeA));
}
RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderParentReceivable) {
/* R is backward projected on C
A
/ \
B C
|
R
*/
auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
[](RenderProperties& props, SkiaRecordingCanvas& canvas) {
drawOrderedNode(&canvas, 0, nullptr); //nodeB
drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
props.setProjectionReceiver(true);
drawOrderedNode(&canvas, 2, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
props.setProjectBackwards(true);
props.setClipToBounds(false);
} ); //nodeR
} ); //nodeC
}); //nodeA
EXPECT_EQ(3, drawNode(renderThread, nodeA));
}
RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderSameNodeReceivable) {
/* R is backward projected on R
A
/ \
B C
|
R
*/
auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
[](RenderProperties& props, SkiaRecordingCanvas& canvas) {
drawOrderedNode(&canvas, 0, nullptr); //nodeB
drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
drawOrderedNode(&canvas, 255, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
//having a node that is projected on itself is an undefined/unexpected behavior
props.setProjectionReceiver(true);
props.setProjectBackwards(true);
props.setClipToBounds(false);
} ); //nodeR
} ); //nodeC
}); //nodeA
EXPECT_EQ(2, drawNode(renderThread, nodeA));
}
//Note: the outcome for this test is different in HWUI
RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderProjectedSibling) {
/* R is set to project on B, but R is not drawn because projecting on a sibling is not allowed.
A
/|\
/ | \
B C R
*/
auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
[](RenderProperties& props, SkiaRecordingCanvas& canvas) {
drawOrderedNode(&canvas, 0, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
props.setProjectionReceiver(true);
} ); //nodeB
drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
} ); //nodeC
drawOrderedNode(&canvas, 255, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
props.setProjectBackwards(true);
props.setClipToBounds(false);
} ); //nodeR
}); //nodeA
EXPECT_EQ(2, drawNode(renderThread, nodeA));
}
RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderProjectedSibling2) {
/* R is set to project on B, but R is not drawn because projecting on a sibling is not allowed.
A
|
G
/|\
/ | \
B C R
*/
auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
[](RenderProperties& props, SkiaRecordingCanvas& canvas) {
drawOrderedNode(&canvas, 0, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
props.setProjectionReceiver(true);
} ); //nodeB
drawOrderedNode(&canvas, 2, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
} ); //nodeC
drawOrderedNode(&canvas, 255, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
props.setProjectBackwards(true);
props.setClipToBounds(false);
} ); //nodeR
} ); //nodeG
}); //nodeA
EXPECT_EQ(3, drawNode(renderThread, nodeA));
}
RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderGrandparentReceivable) {
/* R is backward projected on B
A
|
B
|
C
|
R
*/
auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
[](RenderProperties& props, SkiaRecordingCanvas& canvas) {
drawOrderedNode(&canvas, 0, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
props.setProjectionReceiver(true);
drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
drawOrderedNode(&canvas, 2, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
props.setProjectBackwards(true);
props.setClipToBounds(false);
} ); //nodeR
} ); //nodeC
} ); //nodeB
}); //nodeA
EXPECT_EQ(3, drawNode(renderThread, nodeA));
}
RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderTwoReceivables) {
/* B and G are receivables, R is backward projected
A
/ \
B C
/ \
G R
*/
auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
[](RenderProperties& props, SkiaRecordingCanvas& canvas) {
drawOrderedNode(&canvas, 0, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //B
props.setProjectionReceiver(true);
} ); //nodeB
drawOrderedNode(&canvas, 2, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //C
drawOrderedNode(&canvas, 3, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //G
props.setProjectionReceiver(true);
} ); //nodeG
drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //R
props.setProjectBackwards(true);
props.setClipToBounds(false);
} ); //nodeR
} ); //nodeC
}); //nodeA
EXPECT_EQ(4, drawNode(renderThread, nodeA));
}
RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderTwoReceivablesLikelyScenario) {
/* B and G are receivables, G is backward projected
A
/ \
B C
/ \
G R
*/
auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
[](RenderProperties& props, SkiaRecordingCanvas& canvas) {
drawOrderedNode(&canvas, 0, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //B
props.setProjectionReceiver(true);
} ); //nodeB
drawOrderedNode(&canvas, 2, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //C
drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //G
props.setProjectionReceiver(true);
props.setProjectBackwards(true);
props.setClipToBounds(false);
} ); //nodeG
drawOrderedNode(&canvas, 3, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //R
} ); //nodeR
} ); //nodeC
}); //nodeA
EXPECT_EQ(4, drawNode(renderThread, nodeA));
}
RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderTwoReceivablesDeeper) {
/* B and G are receivables, R is backward projected
A
/ \
B C
/ \
G D
|
R
*/
auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
[](RenderProperties& props, SkiaRecordingCanvas& canvas) {
drawOrderedNode(&canvas, 0, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //B
props.setProjectionReceiver(true);
} ); //nodeB
drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //C
drawOrderedNode(&canvas, 2, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //G
props.setProjectionReceiver(true);
} ); //nodeG
drawOrderedNode(&canvas, 4, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //D
drawOrderedNode(&canvas, 3, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //R
props.setProjectBackwards(true);
props.setClipToBounds(false);
} ); //nodeR
} ); //nodeD
} ); //nodeC
}); //nodeA
EXPECT_EQ(5, drawNode(renderThread, nodeA));
}

View File

@@ -33,7 +33,7 @@ TEST(SkiaDisplayList, create) {
SkRect bounds = SkRect::MakeWH(200, 200);
SkiaDisplayList skiaDL(bounds);
ASSERT_TRUE(skiaDL.isEmpty());
ASSERT_FALSE(skiaDL.mIsProjectionReceiver);
ASSERT_FALSE(skiaDL.mProjectionReceiver);
ASSERT_EQ(skiaDL.mDrawable->getBounds(), bounds);
}
@@ -42,12 +42,13 @@ TEST(SkiaDisplayList, reset) {
SkiaDisplayList skiaDL(bounds);
SkCanvas dummyCanvas;
RenderNodeDrawable drawable(nullptr, &dummyCanvas);
skiaDL.mChildNodes.emplace_back(nullptr, &dummyCanvas);
skiaDL.mChildFunctors.emplace_back(nullptr, nullptr, &dummyCanvas);
skiaDL.mMutableImages.push_back(nullptr);
skiaDL.mVectorDrawables.push_back(nullptr);
skiaDL.mDrawable->drawAnnotation(bounds, "testAnnotation", nullptr);
skiaDL.mIsProjectionReceiver = true;
skiaDL.mProjectionReceiver = &drawable;
ASSERT_EQ(skiaDL.mDrawable->getBounds(), bounds);
ASSERT_FALSE(skiaDL.mChildNodes.empty());
@@ -55,7 +56,7 @@ TEST(SkiaDisplayList, reset) {
ASSERT_FALSE(skiaDL.mMutableImages.empty());
ASSERT_FALSE(skiaDL.mVectorDrawables.empty());
ASSERT_FALSE(skiaDL.isEmpty());
ASSERT_TRUE(skiaDL.mIsProjectionReceiver);
ASSERT_TRUE(skiaDL.mProjectionReceiver);
bounds = SkRect::MakeWH(100, 100);
skiaDL.reset(bounds);
@@ -66,7 +67,7 @@ TEST(SkiaDisplayList, reset) {
ASSERT_TRUE(skiaDL.mMutableImages.empty());
ASSERT_TRUE(skiaDL.mVectorDrawables.empty());
ASSERT_TRUE(skiaDL.isEmpty());
ASSERT_FALSE(skiaDL.mIsProjectionReceiver);
ASSERT_FALSE(skiaDL.mProjectionReceiver);
}
TEST(SkiaDisplayList, reuseDisplayList) {