Files
frameworks_base/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
Peiyong Lin 1f6aa122a5 [HWUI] Implement legacy color mode.
Previously, HWUI always produces SRGB buffers. We introduced new APIs for
SurfaceFlinger, a.k.a. the composer service to return to composition preference
for data space, and pixel format. This patch makes HWUI query composition
preference from composer service, and creates the corresponding EGL surface
with the correct attributes.

In legacy mode, HWUI will take the pixel value from source color space, and
interpret it as pixel value in destination color space.

BUG: 111436479
BUG: 113530681
Test: Build, flash, boot and check dumpsys SurfaceFlinger
Change-Id: I64562d5ea6f653076c8b448feb56b5e0624bc81c
2018-09-13 13:50:27 -07:00

1210 lines
52 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 <VectorDrawable.h>
#include <gtest/gtest.h>
#include <SkClipStack.h>
#include <SkSurface_Base.h>
#include <string.h>
#include "AnimationContext.h"
#include "DamageAccumulator.h"
#include "FatalTestCanvas.h"
#include "IContextFactory.h"
#include "RecordingCanvas.h"
#include "SkiaCanvas.h"
#include "pipeline/skia/SkiaDisplayList.h"
#include "pipeline/skia/SkiaOpenGLPipeline.h"
#include "pipeline/skia/SkiaPipeline.h"
#include "pipeline/skia/SkiaRecordingCanvas.h"
#include "renderthread/CanvasContext.h"
#include "tests/common/TestUtils.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);
});
DisplayListData skLiteDL;
RecordingCanvas canvas;
canvas.reset(&skLiteDL, SkIRect::MakeWH(1, 1));
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;
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_SKIA_PIPELINE_TEST(RenderNodeDrawable, emptyReceiver) {
class ProjectionTestCanvas : public SkCanvas {
public:
ProjectionTestCanvas(int width, int height) : SkCanvas(width, height) {}
void onDrawRect(const SkRect& rect, const SkPaint& paint) override {
mDrawCounter++;
}
int getDrawCounter() { return mDrawCounter; }
private:
int mDrawCounter = 0;
};
auto receiverBackground = TestUtils::createSkiaNode(
0, 0, 100, 100,
[](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
properties.setProjectionReceiver(true);
},
"B"); // a receiver with an empty display list
auto projectingRipple = TestUtils::createSkiaNode(
0, 0, 100, 100,
[](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
properties.setProjectBackwards(true);
properties.setClipToBounds(false);
SkPaint paint;
canvas.drawRect(0, 0, 100, 100, paint);
},
"P");
auto child = TestUtils::createSkiaNode(
0, 0, 100, 100,
[&projectingRipple](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
SkPaint paint;
canvas.drawRect(0, 0, 100, 100, paint);
canvas.drawRenderNode(projectingRipple.get());
},
"C");
auto parent = TestUtils::createSkiaNode(
0, 0, 100, 100,
[&receiverBackground, &child](RenderProperties& properties,
SkiaRecordingCanvas& canvas) {
canvas.drawRenderNode(receiverBackground.get());
canvas.drawRenderNode(child.get());
},
"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;
parent->prepareTree(info);
// parent(A) -> (receiverBackground, child)
// child(C) -> (rect[0, 0, 100, 100], projectingRipple)
// projectingRipple(P) -> (rect[0, 0, 100, 100]) -> projects backwards
// receiverBackground(B) -> (empty) -> 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(2, canvas.getDrawCounter());
}
RENDERTHREAD_SKIA_PIPELINE_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) {}
virtual sk_sp<SkImage> onNewImageSnapshot() 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()));
return nullptr;
}
SkCanvas* onNewCanvas() override { return new ProjectionTestCanvas(mDrawCounter); }
sk_sp<SkSurface> onNewSurface(const SkImageInfo&) override { return nullptr; }
void onCopyOnWrite(ContentChangeMode) override {}
int* mDrawCounter;
void onWritePixels(const SkPixmap&, int x, int y) {}
};
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;
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));
auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
pipeline->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;
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;
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:
EXPECT_EQ(SkRect::MakeWH(CANVAS_WIDTH, CANVAS_HEIGHT),
TestUtils::getClipBounds(this));
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);
}
// Verify that layers are composed with kLow_SkFilterQuality filter quality.
RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, layerComposeQuality) {
static const int CANVAS_WIDTH = 1;
static const int CANVAS_HEIGHT = 1;
static const int LAYER_WIDTH = 1;
static const int LAYER_HEIGHT = 1;
class FrameTestCanvas : public TestCanvasBase {
public:
FrameTestCanvas() : TestCanvasBase(CANVAS_WIDTH, CANVAS_HEIGHT) {}
void onDrawImageRect(const SkImage* image, const SkRect* src, const SkRect& dst,
const SkPaint* paint, SrcRectConstraint constraint) override {
mDrawCounter++;
EXPECT_EQ(kLow_SkFilterQuality, paint->getFilterQuality());
}
};
auto layerNode = TestUtils::createSkiaNode(
0, 0, LAYER_WIDTH, LAYER_HEIGHT,
[](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
canvas.drawPaint(SkPaint());
});
layerNode->animatorProperties().mutateLayerProperties().setType(LayerType::RenderLayer);
layerNode->setLayerSurface(SkSurface::MakeRasterN32Premul(LAYER_WIDTH, LAYER_HEIGHT));
FrameTestCanvas canvas;
RenderNodeDrawable drawable(layerNode.get(), &canvas, true);
canvas.drawDrawable(&drawable);
EXPECT_EQ(1, canvas.mDrawCounter); //make sure the layer was composed
// clean up layer pointer, so we can safely destruct RenderNode
layerNode->setLayerSurface(nullptr);
}
TEST(ReorderBarrierDrawable, testShadowMatrix) {
static const int CANVAS_WIDTH = 100;
static const int CANVAS_HEIGHT = 100;
static const float TRANSLATE_X = 11.0f;
static const float TRANSLATE_Y = 22.0f;
static const float CASTER_X = 40.0f;
static const float CASTER_Y = 40.0f;
static const float CASTER_WIDTH = 20.0f;
static const float CASTER_HEIGHT = 20.0f;
class ShadowTestCanvas : public SkCanvas {
public:
ShadowTestCanvas(int width, int height) : SkCanvas(width, height) {}
int getDrawCounter() { return mDrawCounter; }
virtual void onDrawDrawable(SkDrawable* drawable, const SkMatrix* matrix) override {
// expect to draw 2 RenderNodeDrawable, 1 StartReorderBarrierDrawable,
// 1 EndReorderBarrierDrawable
mDrawCounter++;
SkCanvas::onDrawDrawable(drawable, matrix);
}
virtual void didTranslate(SkScalar dx, SkScalar dy) override {
mDrawCounter++;
EXPECT_EQ(dx, TRANSLATE_X);
EXPECT_EQ(dy, TRANSLATE_Y);
}
virtual void didSetMatrix(const SkMatrix& matrix) override {
mDrawCounter++;
// First invocation is EndReorderBarrierDrawable::drawShadow to apply shadow matrix.
// Second invocation is preparing the matrix for an elevated RenderNodeDrawable.
EXPECT_TRUE(matrix.isIdentity());
EXPECT_TRUE(getTotalMatrix().isIdentity());
}
virtual void didConcat(const SkMatrix& matrix) override {
mDrawCounter++;
if (mFirstDidConcat) {
// First invocation is EndReorderBarrierDrawable::drawShadow to apply shadow matrix.
mFirstDidConcat = false;
EXPECT_EQ(SkMatrix::MakeTrans(CASTER_X + TRANSLATE_X, CASTER_Y + TRANSLATE_Y),
matrix);
EXPECT_EQ(SkMatrix::MakeTrans(CASTER_X + TRANSLATE_X, CASTER_Y + TRANSLATE_Y),
getTotalMatrix());
} else {
// Second invocation is preparing the matrix for an elevated RenderNodeDrawable.
EXPECT_EQ(SkMatrix::MakeTrans(TRANSLATE_X, TRANSLATE_Y),
matrix);
EXPECT_EQ(SkMatrix::MakeTrans(TRANSLATE_X, TRANSLATE_Y),
getTotalMatrix());
}
}
protected:
int mDrawCounter = 0;
private:
bool mFirstDidConcat = true;
};
auto parent = TestUtils::createSkiaNode(
0, 0, CANVAS_WIDTH, CANVAS_HEIGHT,
[](RenderProperties& props, SkiaRecordingCanvas& canvas) {
canvas.translate(TRANSLATE_X, TRANSLATE_Y);
canvas.insertReorderBarrier(true);
auto node = TestUtils::createSkiaNode(
CASTER_X, CASTER_Y, CASTER_X + CASTER_WIDTH, CASTER_Y + CASTER_HEIGHT,
[](RenderProperties& props, SkiaRecordingCanvas& canvas) {
props.setElevation(42);
props.mutableOutline().setRoundRect(0, 0, 20, 20, 5, 1);
props.mutableOutline().setShouldClip(true);
});
canvas.drawRenderNode(node.get());
canvas.insertReorderBarrier(false);
});
// create a canvas not backed by any device/pixels, but with dimensions to avoid quick rejection
ShadowTestCanvas canvas(CANVAS_WIDTH, CANVAS_HEIGHT);
RenderNodeDrawable drawable(parent.get(), &canvas, false);
canvas.drawDrawable(&drawable);
EXPECT_EQ(9, canvas.getDrawCounter());
}
// Draw a vector drawable twice but with different bounds and verify correct bounds are used.
RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaRecordingCanvas, drawVectorDrawable) {
static const int CANVAS_WIDTH = 100;
static const int CANVAS_HEIGHT = 200;
class VectorDrawableTestCanvas : public TestCanvasBase {
public:
VectorDrawableTestCanvas() : TestCanvasBase(CANVAS_WIDTH, CANVAS_HEIGHT) {}
void onDrawBitmapRect(const SkBitmap& bitmap, const SkRect* src, const SkRect& dst,
const SkPaint* paint, SrcRectConstraint constraint) override {
const int index = mDrawCounter++;
switch (index) {
case 0:
EXPECT_EQ(dst, SkRect::MakeWH(CANVAS_WIDTH, CANVAS_HEIGHT));
break;
case 1:
EXPECT_EQ(dst, SkRect::MakeWH(CANVAS_WIDTH/2, CANVAS_HEIGHT));
break;
default:
ADD_FAILURE();
}
}
};
VectorDrawable::Group* group = new VectorDrawable::Group();
sp<VectorDrawableRoot> vectorDrawable(new VectorDrawableRoot(group));
vectorDrawable->mutateStagingProperties()->setScaledSize(CANVAS_WIDTH/10, CANVAS_HEIGHT/10);
auto node = TestUtils::createSkiaNode(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT,
[&](RenderProperties& props, SkiaRecordingCanvas& canvas) {
vectorDrawable->mutateStagingProperties()->setBounds(SkRect::MakeWH(CANVAS_WIDTH,
CANVAS_HEIGHT));
canvas.drawVectorDrawable(vectorDrawable.get());
vectorDrawable->mutateStagingProperties()->setBounds(SkRect::MakeWH(CANVAS_WIDTH/2,
CANVAS_HEIGHT));
canvas.drawVectorDrawable(vectorDrawable.get());
});
VectorDrawableTestCanvas canvas;
RenderNodeDrawable drawable(node.get(), &canvas, true);
canvas.drawDrawable(&drawable);
EXPECT_EQ(2, canvas.mDrawCounter);
}