| /* | |
| * 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 <SkBlendMode.h> | |
| #include <SkClipStack.h> | |
| #include <SkSurface_Base.h> | |
| #include <VectorDrawable.h> | |
| #include <gtest/gtest.h> | |
| #include <include/effects/SkImageFilters.h> | |
| #include <string.h> | |
| #include "AnimationContext.h" | |
| #include "DamageAccumulator.h" | |
| #include "FatalTestCanvas.h" | |
| #include "FeatureFlags.h" | |
| #include "IContextFactory.h" | |
| #include "RecordingCanvas.h" | |
| #include "SkiaCanvas.h" | |
| #include "hwui/Paint.h" | |
| #include "pipeline/skia/BackdropFilterDrawable.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" | |
| #include "utils/Color.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) { | |
| Paint 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.enableZ(true); | |
| canvas.enableZ(false); | |
| drawOrderedNode(&canvas, 0, 10.0f); // in reorder=false at this point, so played inorder | |
| drawOrderedRect(&canvas, 1); | |
| canvas.enableZ(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.enableZ(false); | |
| drawOrderedRect(&canvas, 8); | |
| drawOrderedNode(&canvas, 9, -10.0f); // in reorder=false at this point, so played inorder | |
| canvas.enableZ(true); // reorder a node ahead of drawrect op | |
| drawOrderedRect(&canvas, 11); | |
| drawOrderedNode(&canvas, 10, -1.0f); | |
| canvas.enableZ(false); | |
| canvas.enableZ(true); // test with two empty reorder sections | |
| canvas.enableZ(true); | |
| canvas.enableZ(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 = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(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 = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(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 = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(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); | |
| 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::Translate(300.0f, 400.0f), getRecorderMatrix(recorder)); | |
| recorder.restore(); | |
| ASSERT_EQ(SkRect::MakeLTRB(0, 0, 400, 800), getRecorderClipBounds(recorder)); | |
| EXPECT_TRUE(getRecorderMatrix(recorder).isIdentity()); | |
| Paint 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); | |
| Paint 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); | |
| Paint 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) { | |
| Paint 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, 0, 0)); | |
| 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_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); | |
| Paint paint; | |
| canvas.drawRect(0, 0, 100, 100, paint); | |
| }, | |
| "P"); | |
| auto child = TestUtils::createSkiaNode( | |
| 0, 0, 100, 100, | |
| [&projectingRipple](RenderProperties& properties, SkiaRecordingCanvas& canvas) { | |
| Paint 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, 0, 0)); | |
| 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_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(const SkIRect* bounds) 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; } | |
| bool onCopyOnWrite(ContentChangeMode) override { return true; } | |
| 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, Paint()); | |
| }, | |
| "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, Paint()); // 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, Paint()); | |
| }, | |
| "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, 0, 0)); | |
| 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, Paint()); | |
| }, | |
| "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, Paint()); | |
| }, | |
| "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, 0, 0)); | |
| 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, 0, 0)); | |
| 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 onDrawImageRect2(const SkImage*, const SkRect&, const SkRect&, | |
| const SkSamplingOptions&, const SkPaint*, | |
| SrcRectConstraint) 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, | |
| Paint()); | |
| 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, forceInvertBitmapBarcodePalette) { | |
| if (!view_accessibility_flags::force_invert_color()) { | |
| GTEST_SKIP() << "Test only applies when force_invert_color flag is enabled"; | |
| } | |
| Properties::setIsForceInvertEnabled(true); | |
| static const int CANVAS_WIDTH = 100; | |
| static const int CANVAS_HEIGHT = 200; | |
| class BarcodeTestCanvas : public TestCanvasBase { | |
| public: | |
| BarcodeTestCanvas() : TestCanvasBase(CANVAS_WIDTH, CANVAS_HEIGHT) {} | |
| void onDrawRect(const SkRect& rect, const SkPaint& paint) override { | |
| EXPECT_EQ(0, mDrawCounter++); | |
| EXPECT_EQ(SK_ColorWHITE, paint.getColor()); | |
| EXPECT_EQ(SkRect::MakeLTRB(0, 0, 25, 25), rect); | |
| } | |
| void onDrawImageRect2(const SkImage*, const SkRect& src, const SkRect& dest, | |
| const SkSamplingOptions&, const SkPaint* paint, | |
| SrcRectConstraint) override { | |
| EXPECT_EQ(1, mDrawCounter++); | |
| EXPECT_EQ(SkRect::MakeLTRB(0, 0, 25, 25), src); | |
| EXPECT_EQ(SkRect::MakeLTRB(5, 5, 20, 20), dest); | |
| EXPECT_EQ(nullptr, paint->getColorFilter()); | |
| } | |
| }; | |
| auto node = TestUtils::createSkiaNode( | |
| 0, 0, CANVAS_WIDTH, CANVAS_HEIGHT, | |
| [](RenderProperties& props, SkiaRecordingCanvas& canvas) { | |
| sk_sp<Bitmap> bitmap(TestUtils::createBitmap(25, 25)); | |
| bitmap->setPalette(BitmapPalette::Barcode); | |
| canvas.drawBitmap(*bitmap, 0, 0, bitmap->width(), bitmap->height(), 0, 0, | |
| bitmap->width(), bitmap->height(), nullptr); | |
| }); | |
| BarcodeTestCanvas canvas; | |
| RenderNodeDrawable drawable(node.get(), &canvas, true); | |
| canvas.drawDrawable(&drawable); | |
| EXPECT_EQ(2, canvas.mDrawCounter); | |
| Properties::setIsForceInvertEnabled(false); | |
| } | |
| 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) { | |
| Paint 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) { | |
| Paint 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 linear filtering. | |
| RENDERTHREAD_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 onDrawImageRect2(const SkImage* image, const SkRect& src, const SkRect& dst, | |
| const SkSamplingOptions& sampling, const SkPaint* paint, | |
| SrcRectConstraint constraint) override { | |
| mDrawCounter++; | |
| EXPECT_FALSE(sampling.useCubic); | |
| EXPECT_EQ(SkFilterMode::kLinear, sampling.filter); | |
| } | |
| }; | |
| auto layerNode = TestUtils::createSkiaNode( | |
| 0, 0, LAYER_WIDTH, LAYER_HEIGHT, | |
| [](RenderProperties& properties, SkiaRecordingCanvas& canvas) { | |
| canvas.drawPaint(Paint()); | |
| }); | |
| layerNode->animatorProperties().mutateLayerProperties().setType(LayerType::RenderLayer); | |
| layerNode->setLayerSurface(SkSurfaces::Raster(SkImageInfo::MakeN32Premul(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 { | |
| // Do not expect this to be called. See RecordingCanvas.cpp DrawDrawable for context. | |
| EXPECT_TRUE(false); | |
| } | |
| virtual void didTranslate(SkScalar dx, SkScalar dy) override { | |
| mDrawCounter++; | |
| EXPECT_EQ(dx, TRANSLATE_X); | |
| EXPECT_EQ(dy, TRANSLATE_Y); | |
| } | |
| virtual void didSetM44(const SkM44& 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 == SkM44()); | |
| EXPECT_TRUE(getTotalMatrix().isIdentity()); | |
| } | |
| virtual void didConcat44(const SkM44& matrix) override { | |
| mDrawCounter++; | |
| if (mFirstDidConcat) { | |
| // First invocation is EndReorderBarrierDrawable::drawShadow to apply shadow matrix. | |
| mFirstDidConcat = false; | |
| EXPECT_EQ(SkM44::Translate(CASTER_X + TRANSLATE_X, CASTER_Y + TRANSLATE_Y), | |
| matrix); | |
| EXPECT_EQ(SkMatrix::Translate(CASTER_X + TRANSLATE_X, CASTER_Y + TRANSLATE_Y), | |
| getTotalMatrix()); | |
| } else { | |
| // Second invocation is preparing the matrix for an elevated RenderNodeDrawable. | |
| EXPECT_EQ(SkM44::Translate(TRANSLATE_X, TRANSLATE_Y), matrix); | |
| EXPECT_EQ(SkMatrix::Translate(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.enableZ(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.enableZ(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); | |
| drawable.draw(&canvas); | |
| EXPECT_EQ(5, canvas.getDrawCounter()); | |
| } | |
| // Draw a vector drawable twice but with different bounds and verify correct bounds are used. | |
| RENDERTHREAD_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 onDrawImageRect2(const SkImage*, const SkRect& src, const SkRect& dst, | |
| const SkSamplingOptions&, 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); | |
| } | |
| // Verify drawing logics for BackdropFilterDrawable | |
| RENDERTHREAD_TEST(BackdropFilterDrawable, drawing) { | |
| static const int CANVAS_WIDTH = 100; | |
| static const int CANVAS_HEIGHT = 200; | |
| class SimpleTestCanvas : public TestCanvasBase { | |
| public: | |
| SkRect mDstBounds; | |
| SimpleTestCanvas() : TestCanvasBase(CANVAS_WIDTH, CANVAS_HEIGHT) {} | |
| void onDrawRect(const SkRect& rect, const SkPaint& paint) override { | |
| // did nothing. | |
| } | |
| // called when BackdropFilterDrawable is drawn. | |
| void onDrawImageRect2(const SkImage*, const SkRect& src, const SkRect& dst, | |
| const SkSamplingOptions&, const SkPaint*, | |
| SrcRectConstraint) override { | |
| mDrawCounter++; | |
| mDstBounds = dst; | |
| } | |
| }; | |
| class SimpleLayer : public SkSurface_Base { | |
| public: | |
| SimpleLayer() | |
| : SkSurface_Base(SkImageInfo::MakeN32Premul(CANVAS_WIDTH, CANVAS_HEIGHT), nullptr) { | |
| } | |
| virtual sk_sp<SkImage> onNewImageSnapshot(const SkIRect* bounds) override { | |
| SkBitmap bitmap; | |
| bitmap.allocN32Pixels(CANVAS_WIDTH, CANVAS_HEIGHT); | |
| bitmap.setImmutable(); | |
| return bitmap.asImage(); | |
| } | |
| SkCanvas* onNewCanvas() override { return new SimpleTestCanvas(); } | |
| sk_sp<SkSurface> onNewSurface(const SkImageInfo&) override { return nullptr; } | |
| bool onCopyOnWrite(ContentChangeMode) override { return true; } | |
| void onWritePixels(const SkPixmap&, int x, int y) {} | |
| }; | |
| auto node = TestUtils::createSkiaNode(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT, | |
| [](RenderProperties& props, SkiaRecordingCanvas& canvas) { | |
| canvas.drawRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT, | |
| Paint()); | |
| }); | |
| sk_sp<SkSurface> surface(new SimpleLayer()); | |
| auto* canvas = reinterpret_cast<SimpleTestCanvas*>(surface->getCanvas()); | |
| RenderNodeDrawable drawable(node.get(), canvas, true); | |
| BackdropFilterDrawable backdropDrawable(node.get(), canvas); | |
| canvas->drawDrawable(&drawable); | |
| canvas->drawDrawable(&backdropDrawable); | |
| // no backdrop filter, skip drawing. | |
| EXPECT_EQ(0, canvas->mDrawCounter); | |
| sk_sp<SkImageFilter> filter(SkImageFilters::Blur(3, 3, nullptr)); | |
| node->animatorProperties().mutateLayerProperties().setBackdropImageFilter(filter.get()); | |
| canvas->drawDrawable(&drawable); | |
| canvas->drawDrawable(&backdropDrawable); | |
| // backdrop filter is set, ok to draw. | |
| EXPECT_EQ(1, canvas->mDrawCounter); | |
| EXPECT_EQ(SkRect::MakeLTRB(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT), canvas->mDstBounds); | |
| canvas->translate(30, 30); | |
| canvas->drawDrawable(&drawable); | |
| canvas->drawDrawable(&backdropDrawable); | |
| // the drawable is still visible, ok to draw. | |
| EXPECT_EQ(2, canvas->mDrawCounter); | |
| EXPECT_EQ(SkRect::MakeLTRB(30, 30, CANVAS_WIDTH, CANVAS_HEIGHT), canvas->mDstBounds); | |
| canvas->translate(CANVAS_WIDTH, CANVAS_HEIGHT); | |
| canvas->drawDrawable(&drawable); | |
| canvas->drawDrawable(&backdropDrawable); | |
| // the drawable is invisible, skip drawing. | |
| EXPECT_EQ(2, canvas->mDrawCounter); | |
| } |