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
950 lines
39 KiB
C++
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);
|
|
}
|
|
|