Fix Skia render node projection to match HWUI
Fix Skia render node projection to match HWUI. Port FrameBuilderTests_projectionReorder test for Skia pipeline. Add new tests in both HWUI and Skia to cover more projection use cases. Test: built and run on angler-eng Change-Id: Ibf27af211452ae95d595aca7723ea63f48b0b282
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user