Files
frameworks_base/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
Stan Iliev 68885e38b8 Modify savelayer to save matrix and clip for Skia pipeline
Modify SkiaCanvas::saveLayer to always save matrix and clip and
match HWUI behaviour. Also ensure android state tracking
behavior matches that of the Skia API (partial saves not
supported). This change is fixing SaveLayerAnimation
macrobench when buffer age is disabled.
Add a HWUI unit test that verifies clip and matrix are restored.

Test: built and ran angler-eng, ran hwui unit tests
bug:33429678
Change-Id: I62e429f9746518fef67663b0dd99ac499bf31af3
2016-12-15 16:47:34 -05:00

950 lines
39 KiB
C++

/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <gtest/gtest.h>
#include <VectorDrawable.h>
#include "AnimationContext.h"
#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 "FatalTestCanvas.h"
#include <string.h>
using namespace android;
using namespace android::uirenderer;
using namespace android::uirenderer::renderthread;
using namespace android::uirenderer::skiapipeline;
TEST(RenderNodeDrawable, create) {
auto rootNode = TestUtils::createNode(0, 0, 200, 400,
[](RenderProperties& props, Canvas& canvas) {
canvas.drawColor(Color::Red_500, SkBlendMode::kSrcOver);
});
auto skLiteDL = SkLiteDL::New(SkRect::MakeWH(1, 1));
SkLiteRecorder canvas;
canvas.reset(skLiteDL.get());
canvas.translate(100, 100);
RenderNodeDrawable drawable(rootNode.get(), &canvas);
ASSERT_EQ(drawable.getRenderNode(), rootNode.get());
ASSERT_EQ(&drawable.getNodeProperties(), &rootNode->properties());
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
paint.setColor(SkColorSetARGB(1, 0, 0, expectedDrawOrder));
canvas->drawRect(0, 0, 100, 100, paint);
}
static void drawOrderedNode(Canvas* canvas, uint8_t expectedDrawOrder, float z) {
auto node = TestUtils::createSkiaNode(0, 0, 100, 100,
[expectedDrawOrder, z](RenderProperties& props, SkiaRecordingCanvas& canvas) {
drawOrderedRect(&canvas, expectedDrawOrder);
props.setTranslationZ(z);
});
canvas->drawRenderNode(node.get()); // canvas takes reference/sole ownership
}
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);
}
});
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, mDrawCounter++) << "An op was drawn out of order";
}
int getIndex() { return mDrawCounter; }
protected:
int mDrawCounter = 0;
};
} // end anonymous namespace
TEST(RenderNodeDrawable, zReorder) {
auto parent = TestUtils::createSkiaNode(0, 0, 100, 100,
[](RenderProperties& props, SkiaRecordingCanvas& canvas) {
canvas.insertReorderBarrier(true);
canvas.insertReorderBarrier(false);
drawOrderedNode(&canvas, 0, 10.0f); // in reorder=false at this point, so played inorder
drawOrderedRect(&canvas, 1);
canvas.insertReorderBarrier(true);
drawOrderedNode(&canvas, 6, 2.0f);
drawOrderedRect(&canvas, 3);
drawOrderedNode(&canvas, 4, 0.0f);
drawOrderedRect(&canvas, 5);
drawOrderedNode(&canvas, 2, -2.0f);
drawOrderedNode(&canvas, 7, 2.0f);
canvas.insertReorderBarrier(false);
drawOrderedRect(&canvas, 8);
drawOrderedNode(&canvas, 9, -10.0f); // in reorder=false at this point, so played inorder
canvas.insertReorderBarrier(true); //reorder a node ahead of drawrect op
drawOrderedRect(&canvas, 11);
drawOrderedNode(&canvas, 10, -1.0f);
canvas.insertReorderBarrier(false);
canvas.insertReorderBarrier(true); //test with two empty reorder sections
canvas.insertReorderBarrier(true);
canvas.insertReorderBarrier(false);
drawOrderedRect(&canvas, 12);
});
//create a canvas not backed by any device/pixels, but with dimensions to avoid quick rejection
ZReorderCanvas canvas(100, 100);
RenderNodeDrawable drawable(parent.get(), &canvas, false);
canvas.drawDrawable(&drawable);
EXPECT_EQ(13, canvas.getIndex());
}
TEST(RenderNodeDrawable, composeOnLayer)
{
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);
auto rootNode = TestUtils::createSkiaNode(0, 0, 1, 1,
[](RenderProperties& props, SkiaRecordingCanvas& recorder) {
recorder.drawColor(SK_ColorRED, SkBlendMode::kSrcOver);
});
//attach a layer to the render node
auto surfaceLayer = SkSurface::MakeRasterN32Premul(1, 1);
auto canvas2 = surfaceLayer->getCanvas();
canvas2->drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver);
rootNode->setLayerSurface(surfaceLayer);
RenderNodeDrawable drawable1(rootNode.get(), &canvas, false);
canvas.drawDrawable(&drawable1);
ASSERT_EQ(SK_ColorRED, TestUtils::getColor(surface, 0, 0));
RenderNodeDrawable drawable2(rootNode.get(), &canvas, true);
canvas.drawDrawable(&drawable2);
ASSERT_EQ(SK_ColorWHITE, TestUtils::getColor(surface, 0, 0));
RenderNodeDrawable drawable3(rootNode.get(), &canvas, false);
canvas.drawDrawable(&drawable3);
ASSERT_EQ(SK_ColorRED, TestUtils::getColor(surface, 0, 0));
rootNode->setLayerSurface(sk_sp<SkSurface>());
}
namespace {
static SkRect getRecorderClipBounds(const SkiaRecordingCanvas& recorder) {
SkRect clipBounds;
recorder.getClipBounds(&clipBounds);
return clipBounds;
}
static SkMatrix getRecorderMatrix(const SkiaRecordingCanvas& recorder) {
SkMatrix matrix;
recorder.getMatrix(&matrix);
return matrix;
}
}
TEST(RenderNodeDrawable, saveLayerClipAndMatrixRestore)
{
auto surface = SkSurface::MakeRasterN32Premul(400, 800);
SkCanvas& canvas = *surface->getCanvas();
canvas.drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver);
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorWHITE);
auto rootNode = TestUtils::createSkiaNode(0, 0, 400, 800,
[](RenderProperties& props, SkiaRecordingCanvas& recorder) {
SkPaint layerPaint;
ASSERT_EQ(SkRect::MakeLTRB(0, 0, 400, 800), getRecorderClipBounds(recorder));
EXPECT_TRUE(getRecorderMatrix(recorder).isIdentity());
//note we don't pass SaveFlags::MatrixClip, but matrix and clip will be saved
recorder.saveLayer(0, 0, 400, 400, &layerPaint, SaveFlags::ClipToLayer);
ASSERT_EQ(SkRect::MakeLTRB(0, 0, 400, 400), getRecorderClipBounds(recorder));
EXPECT_TRUE(getRecorderMatrix(recorder).isIdentity());
recorder.clipRect(50, 50, 350, 350, SkClipOp::kIntersect);
ASSERT_EQ(SkRect::MakeLTRB(50, 50, 350, 350), getRecorderClipBounds(recorder));
recorder.translate(300.0f, 400.0f);
EXPECT_EQ(SkMatrix::MakeTrans(300.0f, 400.0f), getRecorderMatrix(recorder));
recorder.restore();
ASSERT_EQ(SkRect::MakeLTRB(0, 0, 400, 800), getRecorderClipBounds(recorder));
EXPECT_TRUE(getRecorderMatrix(recorder).isIdentity());
SkPaint paint;
paint.setAntiAlias(true);
paint.setColor(SK_ColorGREEN);
recorder.drawRect(0.0f, 400.0f, 400.0f, 800.0f, paint);
});
RenderNodeDrawable drawable(rootNode.get(), &canvas, true);
canvas.drawDrawable(&drawable);
ASSERT_EQ(SK_ColorGREEN, TestUtils::getColor(surface, 200, 600));
}
namespace {
class ContextFactory : public IContextFactory {
public:
virtual AnimationContext* createAnimationContext(renderthread::TimeLord& clock) override {
return new AnimationContext(clock);
}
};
} // 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 = mDrawCounter++;
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), TestUtils::getClipBounds(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), TestUtils::getLocalClipBounds(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), TestUtils::getClipBounds(this));
break;
default:
ADD_FAILURE();
}
EXPECT_EQ(expectedMatrix, getTotalMatrix());
}
int getIndex() { return mDrawCounter; }
protected:
int mDrawCounter = 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(int* drawCounter)
: SkCanvas(CANVAS_WIDTH, CANVAS_HEIGHT)
, mDrawCounter(drawCounter)
{}
void onDrawArc(const SkRect&, SkScalar startAngle, SkScalar sweepAngle, bool useCenter,
const SkPaint&) override {
EXPECT_EQ(0, (*mDrawCounter)++); //part of painting the layer
EXPECT_EQ(SkRect::MakeLTRB(0, 0, LAYER_WIDTH, LAYER_HEIGHT), TestUtils::getClipBounds(this));
}
void onDrawRect(const SkRect& rect, const SkPaint& paint) override {
EXPECT_EQ(1, (*mDrawCounter)++);
EXPECT_EQ(SkRect::MakeLTRB(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT), TestUtils::getClipBounds(this));
}
void onDrawOval(const SkRect&, const SkPaint&) override {
EXPECT_EQ(2, (*mDrawCounter)++);
SkMatrix expectedMatrix;
expectedMatrix.setTranslate(100 - SCROLL_X, 100 - SCROLL_Y);
EXPECT_EQ(expectedMatrix, getTotalMatrix());
EXPECT_EQ(SkRect::MakeLTRB(-85, -80, 295, 300), TestUtils::getLocalClipBounds(this));
}
int* mDrawCounter;
};
class ProjectionLayer : public SkSurface_Base {
public:
ProjectionLayer(int* drawCounter)
: SkSurface_Base(SkImageInfo::MakeN32Premul(LAYER_WIDTH, LAYER_HEIGHT), nullptr)
, mDrawCounter(drawCounter) {
}
void onDraw(SkCanvas*, SkScalar x, SkScalar y, const SkPaint*) override {
EXPECT_EQ(3, (*mDrawCounter)++);
EXPECT_EQ(SkRect::MakeLTRB(100 - SCROLL_X, 100 - SCROLL_Y, 300 - SCROLL_X,
300 - SCROLL_Y), TestUtils::getClipBounds(this->getCanvas()));
}
SkCanvas* onNewCanvas() override {
return new ProjectionTestCanvas(mDrawCounter);
}
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 {}
int* mDrawCounter;
};
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);
int drawCounter = 0;
//set a layer after prepareTree to avoid layer logic there
child->animatorProperties().mutateLayerProperties().setType(LayerType::RenderLayer);
sk_sp<SkSurface> surfaceLayer1(new ProjectionLayer(&drawCounter));
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, drawCounter); //assert index 0 is drawn on the layer
RenderNodeDrawable drawable(parent.get(), surfaceLayer1->getCanvas(), true);
surfaceLayer1->getCanvas()->drawDrawable(&drawable);
EXPECT_EQ(4, drawCounter);
// 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, mDrawCounter++);
EXPECT_TRUE(getTotalMatrix().isIdentity());
}
void onDrawOval(const SkRect&, const SkPaint&) override {
EXPECT_EQ(1, mDrawCounter++);
EXPECT_EQ(SkRect::MakeWH(CANVAS_WIDTH, CANVAS_HEIGHT), TestUtils::getClipBounds(this));
EXPECT_TRUE(getTotalMatrix().isIdentity());
}
int mDrawCounter = 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, SkClipOp::kIntersect);
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);
std::unique_ptr<ProjectionChildScrollTestCanvas> canvas(new ProjectionChildScrollTestCanvas());
RenderNodeDrawable drawable(parent.get(), canvas.get(), true);
canvas->drawDrawable(&drawable);
EXPECT_EQ(2, canvas->mDrawCounter);
}
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));
}
RENDERTHREAD_TEST(RenderNodeDrawable, simple) {
static const int CANVAS_WIDTH = 100;
static const int CANVAS_HEIGHT = 200;
class SimpleTestCanvas : public TestCanvasBase {
public:
SimpleTestCanvas() : TestCanvasBase(CANVAS_WIDTH, CANVAS_HEIGHT) {
}
void onDrawRect(const SkRect& rect, const SkPaint& paint) override {
EXPECT_EQ(0, mDrawCounter++);
}
void onDrawImage(const SkImage*, SkScalar dx, SkScalar dy, const SkPaint*) override {
EXPECT_EQ(1, mDrawCounter++);
}
};
auto node = TestUtils::createSkiaNode(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT,
[](RenderProperties& props, SkiaRecordingCanvas& canvas) {
sk_sp<Bitmap> bitmap(TestUtils::createBitmap(25, 25));
canvas.drawRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT, SkPaint());
canvas.drawBitmap(*bitmap, 10, 10, nullptr);
});
SimpleTestCanvas canvas;
RenderNodeDrawable drawable(node.get(), &canvas, true);
canvas.drawDrawable(&drawable);
EXPECT_EQ(2, canvas.mDrawCounter);
}
RENDERTHREAD_TEST(RenderNodeDrawable, colorOp_unbounded) {
static const int CANVAS_WIDTH = 200;
static const int CANVAS_HEIGHT = 200;
class ColorTestCanvas : public TestCanvasBase {
public:
ColorTestCanvas() : TestCanvasBase(CANVAS_WIDTH, CANVAS_HEIGHT) {
}
void onDrawPaint(const SkPaint&) {
switch (mDrawCounter++) {
case 0:
// While this mirrors FrameBuilder::colorOp_unbounded, this value is different
// because there is no root (root is clipped in SkiaPipeline::renderFrame).
// SkiaPipeline.clipped and clip_replace verify the root clip.
EXPECT_TRUE(TestUtils::getClipBounds(this).isEmpty());
break;
case 1:
EXPECT_EQ(SkRect::MakeWH(10, 10), TestUtils::getClipBounds(this));
break;
default:
ADD_FAILURE();
}
}
};
auto unclippedColorView = TestUtils::createSkiaNode(0, 0, 10, 10,
[](RenderProperties& props, SkiaRecordingCanvas& canvas) {
props.setClipToBounds(false);
canvas.drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver);
});
auto clippedColorView = TestUtils::createSkiaNode(0, 0, 10, 10,
[](RenderProperties& props, SkiaRecordingCanvas& canvas) {
canvas.drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver);
});
ColorTestCanvas canvas;
RenderNodeDrawable drawable(unclippedColorView.get(), &canvas, true);
canvas.drawDrawable(&drawable);
EXPECT_EQ(1, canvas.mDrawCounter);
RenderNodeDrawable drawable2(clippedColorView.get(), &canvas, true);
canvas.drawDrawable(&drawable2);
EXPECT_EQ(2, canvas.mDrawCounter);
}
TEST(RenderNodeDrawable, renderNode) {
static const int CANVAS_WIDTH = 200;
static const int CANVAS_HEIGHT = 200;
class RenderNodeTestCanvas : public TestCanvasBase {
public:
RenderNodeTestCanvas() : TestCanvasBase(CANVAS_WIDTH, CANVAS_HEIGHT) {
}
void onDrawRect(const SkRect& rect, const SkPaint& paint) override {
switch(mDrawCounter++) {
case 0:
EXPECT_EQ(SkRect::MakeWH(CANVAS_WIDTH, CANVAS_HEIGHT), TestUtils::getClipBounds(this));
EXPECT_EQ(SK_ColorDKGRAY, paint.getColor());
break;
case 1:
EXPECT_EQ(SkRect::MakeLTRB(50, 50, 150, 150), TestUtils::getClipBounds(this));
EXPECT_EQ(SK_ColorWHITE, paint.getColor());
break;
default:
ADD_FAILURE();
}
}
};
auto child = TestUtils::createSkiaNode(10, 10, 110, 110,
[](RenderProperties& props, SkiaRecordingCanvas& canvas) {
SkPaint paint;
paint.setColor(SK_ColorWHITE);
canvas.drawRect(0, 0, 100, 100, paint);
});
auto parent = TestUtils::createSkiaNode(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT,
[&child](RenderProperties& props, SkiaRecordingCanvas& canvas) {
SkPaint paint;
paint.setColor(SK_ColorDKGRAY);
canvas.drawRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT, paint);
canvas.save(SaveFlags::MatrixClip);
canvas.translate(40, 40);
canvas.drawRenderNode(child.get());
canvas.restore();
});
RenderNodeTestCanvas canvas;
RenderNodeDrawable drawable(parent.get(), &canvas, true);
canvas.drawDrawable(&drawable);
EXPECT_EQ(2, canvas.mDrawCounter);
}