diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk index 21ebe5f69c871..b312cb2ef66ad 100644 --- a/libs/hwui/Android.mk +++ b/libs/hwui/Android.mk @@ -307,6 +307,7 @@ LOCAL_SRC_FILES += \ tests/unit/SkiaBehaviorTests.cpp \ tests/unit/SkiaDisplayListTests.cpp \ tests/unit/SkiaPipelineTests.cpp \ + tests/unit/SkiaRenderPropertiesTests.cpp \ tests/unit/SkiaCanvasTests.cpp \ tests/unit/SnapshotTests.cpp \ tests/unit/StringUtilsTests.cpp \ diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp index d77aa4816ccd5..6606b02e09a66 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp @@ -221,6 +221,14 @@ void SkiaPipeline::renderFrame(const LayerUpdateQueue& layers, const SkRect& cli canvas->flush(); } +namespace { +static Rect nodeBounds(RenderNode& node) { + auto& props = node.properties(); + return Rect(props.getLeft(), props.getTop(), + props.getRight(), props.getBottom()); +} +} + void SkiaPipeline::renderFrameImpl(const LayerUpdateQueue& layers, const SkRect& clip, const std::vector>& nodes, bool opaque, const Rect &contentDrawBounds, SkCanvas* canvas) { @@ -231,57 +239,85 @@ void SkiaPipeline::renderFrameImpl(const LayerUpdateQueue& layers, const SkRect& canvas->clear(SK_ColorTRANSPARENT); } - // If there are multiple render nodes, they are laid out as follows: - // #0 - backdrop (content + caption) - // #1 - content (positioned at (0,0) and clipped to - its bounds mContentDrawBounds) - // #2 - additional overlay nodes - // Usually the backdrop cannot be seen since it will be entirely covered by the content. While - // resizing however it might become partially visible. The following render loop will crop the - // backdrop against the content and draw the remaining part of it. It will then draw the content - // cropped to the backdrop (since that indicates a shrinking of the window). - // - // Additional nodes will be drawn on top with no particular clipping semantics. + if (1 == nodes.size()) { + if (!nodes[0]->nothingToDraw()) { + SkAutoCanvasRestore acr(canvas, true); + RenderNodeDrawable root(nodes[0].get(), canvas); + root.draw(canvas); + } + } else if (0 == nodes.size()) { + //nothing to draw + } else { + // It there are multiple render nodes, they are laid out as follows: + // #0 - backdrop (content + caption) + // #1 - content (local bounds are at (0,0), will be translated and clipped to backdrop) + // #2 - additional overlay nodes + // Usually the backdrop cannot be seen since it will be entirely covered by the content. While + // resizing however it might become partially visible. The following render loop will crop the + // backdrop against the content and draw the remaining part of it. It will then draw the content + // cropped to the backdrop (since that indicates a shrinking of the window). + // + // Additional nodes will be drawn on top with no particular clipping semantics. - // The bounds of the backdrop against which the content should be clipped. - Rect backdropBounds = contentDrawBounds; - // Usually the contents bounds should be mContentDrawBounds - however - we will - // move it towards the fixed edge to give it a more stable appearance (for the moment). - // If there is no content bounds we ignore the layering as stated above and start with 2. - int layer = (contentDrawBounds.isEmpty() || nodes.size() == 1) ? 2 : 0; + // Usually the contents bounds should be mContentDrawBounds - however - we will + // move it towards the fixed edge to give it a more stable appearance (for the moment). + // If there is no content bounds we ignore the layering as stated above and start with 2. - for (const sp& node : nodes) { - if (node->nothingToDraw()) continue; + // Backdrop bounds in render target space + const Rect backdrop = nodeBounds(*nodes[0]); - SkASSERT(node->getDisplayList()->isSkiaDL()); + // Bounds that content will fill in render target space (note content node bounds may be bigger) + Rect content(contentDrawBounds.getWidth(), contentDrawBounds.getHeight()); + content.translate(backdrop.left, backdrop.top); + if (!content.contains(backdrop) && !nodes[0]->nothingToDraw()) { + // Content doesn't entirely overlap backdrop, so fill around content (right/bottom) - int count = canvas->save(); - - if (layer == 0) { - const RenderProperties& properties = node->properties(); - Rect targetBounds(properties.getLeft(), properties.getTop(), - properties.getRight(), properties.getBottom()); - // Move the content bounds towards the fixed corner of the backdrop. - const int x = targetBounds.left; - const int y = targetBounds.top; - // Remember the intersection of the target bounds and the intersection bounds against - // which we have to crop the content. - backdropBounds.set(x, y, x + backdropBounds.getWidth(), y + backdropBounds.getHeight()); - backdropBounds.doIntersect(targetBounds); - } else if (layer == 1) { - // We shift and clip the content to match its final location in the window. - const SkRect clip = SkRect::MakeXYWH(contentDrawBounds.left, contentDrawBounds.top, - backdropBounds.getWidth(), backdropBounds.getHeight()); - const float dx = backdropBounds.left - contentDrawBounds.left; - const float dy = backdropBounds.top - contentDrawBounds.top; - canvas->translate(dx, dy); - // It gets cropped against the bounds of the backdrop to stay inside. - canvas->clipRect(clip); + // Note: in the future, if content doesn't snap to backdrop's left/top, this may need to + // also fill left/top. Currently, both 2up and freeform position content at the top/left of + // the backdrop, so this isn't necessary. + RenderNodeDrawable backdropNode(nodes[0].get(), canvas); + if (content.right < backdrop.right) { + // draw backdrop to right side of content + SkAutoCanvasRestore acr(canvas, true); + canvas->clipRect(SkRect::MakeLTRB(content.right, backdrop.top, + backdrop.right, backdrop.bottom)); + backdropNode.draw(canvas); + } + if (content.bottom < backdrop.bottom) { + // draw backdrop to bottom of content + // Note: bottom fill uses content left/right, to avoid overdrawing left/right fill + SkAutoCanvasRestore acr(canvas, true); + canvas->clipRect(SkRect::MakeLTRB(content.left, content.bottom, + content.right, backdrop.bottom)); + backdropNode.draw(canvas); + } } - RenderNodeDrawable root(node.get(), canvas); - root.draw(canvas); - canvas->restoreToCount(count); - layer++; + RenderNodeDrawable contentNode(nodes[1].get(), canvas); + if (!backdrop.isEmpty()) { + // content node translation to catch up with backdrop + float dx = backdrop.left - contentDrawBounds.left; + float dy = backdrop.top - contentDrawBounds.top; + + SkAutoCanvasRestore acr(canvas, true); + canvas->translate(dx, dy); + const SkRect contentLocalClip = SkRect::MakeXYWH(contentDrawBounds.left, + contentDrawBounds.top, backdrop.getWidth(), backdrop.getHeight()); + canvas->clipRect(contentLocalClip); + contentNode.draw(canvas); + } else { + SkAutoCanvasRestore acr(canvas, true); + contentNode.draw(canvas); + } + + // remaining overlay nodes, simply defer + for (size_t index = 2; index < nodes.size(); index++) { + if (!nodes[index]->nothingToDraw()) { + SkAutoCanvasRestore acr(canvas, true); + RenderNodeDrawable overlayNode(nodes[index].get(), canvas); + overlayNode.draw(canvas); + } + } } } diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp index 9530c793e4e49..243e401a83560 100644 --- a/libs/hwui/tests/common/TestUtils.cpp +++ b/libs/hwui/tests/common/TestUtils.cpp @@ -22,6 +22,7 @@ #include #include #include +#include namespace android { namespace uirenderer { @@ -164,5 +165,23 @@ SkColor TestUtils::getColor(const sk_sp& surface, int x, int y) { return 0; } +SkRect TestUtils::getClipBounds(const SkCanvas* canvas) { + SkClipStack::BoundsType boundType; + SkRect clipBounds; + canvas->getClipStack()->getBounds(&clipBounds, &boundType); + return clipBounds; +} + +SkRect TestUtils::getLocalClipBounds(const SkCanvas* canvas) { + SkMatrix invertedTotalMatrix; + if (!canvas->getTotalMatrix().invert(&invertedTotalMatrix)) { + return SkRect::MakeEmpty(); + } + SkRect outlineInDeviceCoord = TestUtils::getClipBounds(canvas); + SkRect outlineInLocalCoord; + invertedTotalMatrix.mapRect(&outlineInLocalCoord, outlineInDeviceCoord); + return outlineInLocalCoord; +} + } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h index 363b94ac492e0..80cbb24355c5a 100644 --- a/libs/hwui/tests/common/TestUtils.h +++ b/libs/hwui/tests/common/TestUtils.h @@ -293,6 +293,9 @@ public: static SkColor getColor(const sk_sp& surface, int x, int y); + static SkRect getClipBounds(const SkCanvas* canvas); + static SkRect getLocalClipBounds(const SkCanvas* canvas); + private: static void syncHierarchyPropertiesAndDisplayListImpl(RenderNode* node) { node->syncProperties(); diff --git a/libs/hwui/tests/unit/FatalTestCanvas.h b/libs/hwui/tests/unit/FatalTestCanvas.h new file mode 100644 index 0000000000000..b4132b7b44ed2 --- /dev/null +++ b/libs/hwui/tests/unit/FatalTestCanvas.h @@ -0,0 +1,143 @@ +/* + * 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. + */ + +#pragma once + +#include +#include + +namespace { + +class TestCanvasBase : public SkCanvas { +public: + TestCanvasBase(int width, int height) : SkCanvas(width, height) { + } + void onDrawAnnotation(const SkRect&, const char key[], SkData* value) { + ADD_FAILURE() << "onDrawAnnotation not expected in this test"; + } + void onDrawDRRect(const SkRRect&, const SkRRect&, const SkPaint&) { + ADD_FAILURE() << "onDrawDRRect not expected in this test"; + } + void onDrawText(const void* text, size_t byteLength, SkScalar x, SkScalar y, + const SkPaint& paint) { + ADD_FAILURE() << "onDrawText not expected in this test"; + } + void onDrawPosText(const void* text, size_t byteLength, const SkPoint pos[], + const SkPaint& paint) { + ADD_FAILURE() << "onDrawPosText not expected in this test"; + } + void onDrawPosTextH(const void* text, size_t byteLength, const SkScalar xpos[], SkScalar constY, + const SkPaint& paint) { + ADD_FAILURE() << "onDrawPosTextH not expected in this test"; + } + void onDrawTextOnPath(const void* text, size_t byteLength, const SkPath& path, + const SkMatrix* matrix, const SkPaint& paint) { + ADD_FAILURE() << "onDrawTextOnPath not expected in this test"; + } + void onDrawTextRSXform(const void* text, size_t byteLength, const SkRSXform[], + const SkRect* cullRect, const SkPaint& paint) { + ADD_FAILURE() << "onDrawTextRSXform not expected in this test"; + } + void onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y, const SkPaint& paint) { + ADD_FAILURE() << "onDrawTextBlob not expected in this test"; + } + void onDrawPatch(const SkPoint cubics[12], const SkColor colors[4], const SkPoint texCoords[4], + SkBlendMode, const SkPaint& paint) { + ADD_FAILURE() << "onDrawPatch not expected in this test"; + } + void onDrawPaint(const SkPaint&) { + ADD_FAILURE() << "onDrawPaint not expected in this test"; + } + void onDrawRect(const SkRect&, const SkPaint&) { + ADD_FAILURE() << "onDrawRect not expected in this test"; + } + void onDrawRegion(const SkRegion& region, const SkPaint& paint) { + ADD_FAILURE() << "onDrawRegion not expected in this test"; + } + void onDrawOval(const SkRect&, const SkPaint&) { + ADD_FAILURE() << "onDrawOval not expected in this test"; + } + void onDrawArc(const SkRect&, SkScalar startAngle, SkScalar sweepAngle, bool useCenter, + const SkPaint&) { + ADD_FAILURE() << "onDrawArc not expected in this test"; + } + void onDrawRRect(const SkRRect&, const SkPaint&) { + ADD_FAILURE() << "onDrawRRect not expected in this test"; + } + void onDrawPoints(PointMode, size_t count, const SkPoint pts[], const SkPaint&) { + ADD_FAILURE() << "onDrawPoints not expected in this test"; + } + void onDrawVertices(VertexMode, int vertexCount, const SkPoint vertices[], const SkPoint texs[], + const SkColor colors[], SkBlendMode, const uint16_t indices[], int indexCount, + const SkPaint&) { + ADD_FAILURE() << "onDrawVertices not expected in this test"; + } + void onDrawAtlas(const SkImage*, const SkRSXform[], const SkRect[], const SkColor[], int count, + SkBlendMode, const SkRect* cull, const SkPaint*) { + ADD_FAILURE() << "onDrawAtlas not expected in this test"; + } + void onDrawPath(const SkPath&, const SkPaint&) { + ADD_FAILURE() << "onDrawPath not expected in this test"; + } + void onDrawImage(const SkImage*, SkScalar dx, SkScalar dy, const SkPaint*) { + ADD_FAILURE() << "onDrawImage not expected in this test"; + } + void onDrawImageRect(const SkImage*, const SkRect*, const SkRect&, const SkPaint*, + SrcRectConstraint) { + ADD_FAILURE() << "onDrawImageRect not expected in this test"; + } + void onDrawImageNine(const SkImage*, const SkIRect& center, const SkRect& dst, const SkPaint*) { + ADD_FAILURE() << "onDrawImageNine not expected in this test"; + } + void onDrawImageLattice(const SkImage*, const Lattice& lattice, const SkRect& dst, + const SkPaint*) { + ADD_FAILURE() << "onDrawImageLattice not expected in this test"; + } + void onDrawBitmap(const SkBitmap&, SkScalar dx, SkScalar dy, const SkPaint*) { + ADD_FAILURE() << "onDrawBitmap not expected in this test"; + } + void onDrawBitmapRect(const SkBitmap&, const SkRect*, const SkRect&, const SkPaint*, + SrcRectConstraint) { + ADD_FAILURE() << "onDrawBitmapRect not expected in this test"; + } + void onDrawBitmapNine(const SkBitmap&, const SkIRect& center, const SkRect& dst, + const SkPaint*) { + ADD_FAILURE() << "onDrawBitmapNine not expected in this test"; + } + void onDrawBitmapLattice(const SkBitmap&, const Lattice& lattice, const SkRect& dst, + const SkPaint*) { + ADD_FAILURE() << "onDrawBitmapLattice not expected in this test"; + } + void onClipRRect(const SkRRect& rrect, ClipOp, ClipEdgeStyle) { + ADD_FAILURE() << "onClipRRect not expected in this test"; + } + void onClipPath(const SkPath& path, ClipOp, ClipEdgeStyle) { + ADD_FAILURE() << "onClipPath not expected in this test"; + } + void onClipRegion(const SkRegion& deviceRgn, ClipOp) { + ADD_FAILURE() << "onClipRegion not expected in this test"; + } + void onDiscard() { + ADD_FAILURE() << "onDiscard not expected in this test"; + } + void onDrawPicture(const SkPicture*, const SkMatrix*, const SkPaint*) { + ADD_FAILURE() << "onDrawPicture not expected in this test"; + } + + int mDrawCounter = 0; //counts how may draw calls of any kind were made to this canvas +}; + +} \ No newline at end of file diff --git a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp index 7c149520f9092..4d4705c0685a6 100644 --- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp +++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp @@ -29,6 +29,7 @@ #include #include #include +#include "FatalTestCanvas.h" #include @@ -89,11 +90,11 @@ public: ZReorderCanvas(int width, int height) : SkCanvas(width, height) {} void onDrawRect(const SkRect& rect, const SkPaint& paint) override { int expectedOrder = SkColorGetB(paint.getColor()); // extract order from blue channel - EXPECT_EQ(expectedOrder, mIndex++) << "An op was drawn out of order"; + EXPECT_EQ(expectedOrder, mDrawCounter++) << "An op was drawn out of order"; } - int getIndex() { return mIndex; } + int getIndex() { return mDrawCounter; } protected: - int mIndex = 0; + int mDrawCounter = 0; }; } // end anonymous namespace @@ -173,21 +174,6 @@ public: return new AnimationContext(clock); } }; - -inline SkRect getBounds(const SkCanvas* canvas) { - SkClipStack::BoundsType boundType; - SkRect clipBounds; - canvas->getClipStack()->getBounds(&clipBounds, &boundType); - return clipBounds; -} -inline SkRect getLocalBounds(const SkCanvas* canvas) { - SkMatrix invertedTotalMatrix; - EXPECT_TRUE(canvas->getTotalMatrix().invert(&invertedTotalMatrix)); - SkRect outlineInDeviceCoord = getBounds(canvas); - SkRect outlineInLocalCoord; - invertedTotalMatrix.mapRect(&outlineInLocalCoord, outlineInDeviceCoord); - return outlineInLocalCoord; -} } // end anonymous namespace RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorder) { @@ -197,26 +183,26 @@ RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorder) { public: ProjectionTestCanvas(int width, int height) : SkCanvas(width, height) {} void onDrawRect(const SkRect& rect, const SkPaint& paint) override { - const int index = mIndex++; + 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), getBounds(this)); + 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), getLocalBounds(this)); + 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), getBounds(this)); + EXPECT_EQ(SkRect::MakeLTRB(0, 40, 95, 90), TestUtils::getClipBounds(this)); break; default: ADD_FAILURE(); @@ -224,9 +210,9 @@ RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorder) { EXPECT_EQ(expectedMatrix, getTotalMatrix()); } - int getIndex() { return mIndex; } + int getIndex() { return mDrawCounter; } protected: - int mIndex = 0; + int mDrawCounter = 0; }; /** @@ -315,23 +301,23 @@ RENDERTHREAD_TEST(RenderNodeDrawable, projectionHwLayer) { public: ProjectionTestCanvas(int* drawCounter) : SkCanvas(CANVAS_WIDTH, CANVAS_HEIGHT) - , mDrawCounter(drawCounter) + , 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), getBounds(this)); + 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), getBounds(this)); + 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), getLocalBounds(this)); + EXPECT_EQ(SkRect::MakeLTRB(-85, -80, 295, 300), TestUtils::getLocalClipBounds(this)); } int* mDrawCounter; }; @@ -345,7 +331,7 @@ RENDERTHREAD_TEST(RenderNodeDrawable, projectionHwLayer) { void onDraw(SkCanvas*, SkScalar x, SkScalar y, const SkPaint*) override { EXPECT_EQ(3, (*mDrawCounter)++); EXPECT_EQ(SkRect::MakeLTRB(100 - SCROLL_X, 100 - SCROLL_Y, 300 - SCROLL_X, - 300 - SCROLL_Y), getBounds(this->getCanvas())); + 300 - SCROLL_Y), TestUtils::getClipBounds(this->getCanvas())); } SkCanvas* onNewCanvas() override { return new ProjectionTestCanvas(mDrawCounter); @@ -440,15 +426,15 @@ RENDERTHREAD_TEST(RenderNodeDrawable, projectionChildScroll) { public: ProjectionChildScrollTestCanvas() : SkCanvas(CANVAS_WIDTH, CANVAS_HEIGHT) {} void onDrawRect(const SkRect& rect, const SkPaint& paint) override { - EXPECT_EQ(0, mIndex++); + EXPECT_EQ(0, mDrawCounter++); EXPECT_TRUE(getTotalMatrix().isIdentity()); } void onDrawOval(const SkRect&, const SkPaint&) override { - EXPECT_EQ(1, mIndex++); - EXPECT_EQ(SkRect::MakeWH(CANVAS_WIDTH, CANVAS_HEIGHT), getBounds(this)); + EXPECT_EQ(1, mDrawCounter++); + EXPECT_EQ(SkRect::MakeWH(CANVAS_WIDTH, CANVAS_HEIGHT), TestUtils::getClipBounds(this)); EXPECT_TRUE(getTotalMatrix().isIdentity()); } - int mIndex = 0; + int mDrawCounter = 0; }; auto receiverBackground = TestUtils::createSkiaNode(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT, @@ -494,7 +480,7 @@ RENDERTHREAD_TEST(RenderNodeDrawable, projectionChildScroll) { std::unique_ptr canvas(new ProjectionChildScrollTestCanvas()); RenderNodeDrawable drawable(parent.get(), canvas.get(), true); canvas->drawDrawable(&drawable); - EXPECT_EQ(2, canvas->mIndex); + EXPECT_EQ(2, canvas->mDrawCounter); } namespace { @@ -787,3 +773,124 @@ RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderTwoReceivablesDeeper) { }); //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(TestUtils::createBitmap(25, 25)); + canvas.drawRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT, SkPaint()); + canvas.drawBitmap(*bitmap, 10, 10, nullptr); + }); + + SimpleTestCanvas canvas; + RenderNodeDrawable drawable(node.get(), &canvas, true); + canvas.drawDrawable(&drawable); + EXPECT_EQ(2, canvas.mDrawCounter); +} + +RENDERTHREAD_TEST(RenderNodeDrawable, colorOp_unbounded) { + static const int CANVAS_WIDTH = 200; + static const int CANVAS_HEIGHT = 200; + class ColorTestCanvas : public TestCanvasBase { + public: + ColorTestCanvas() : TestCanvasBase(CANVAS_WIDTH, CANVAS_HEIGHT) { + } + void onDrawPaint(const SkPaint&) { + switch (mDrawCounter++) { + case 0: + // While this mirrors FrameBuilder::colorOp_unbounded, this value is different + // because there is no root (root is clipped in SkiaPipeline::renderFrame). + // SkiaPipeline.clipped and clip_replace verify the root clip. + EXPECT_TRUE(TestUtils::getClipBounds(this).isEmpty()); + break; + case 1: + EXPECT_EQ(SkRect::MakeWH(10, 10), TestUtils::getClipBounds(this)); + break; + default: + ADD_FAILURE(); + } + } + }; + + auto unclippedColorView = TestUtils::createSkiaNode(0, 0, 10, 10, + [](RenderProperties& props, SkiaRecordingCanvas& canvas) { + props.setClipToBounds(false); + canvas.drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver); + }); + + auto clippedColorView = TestUtils::createSkiaNode(0, 0, 10, 10, + [](RenderProperties& props, SkiaRecordingCanvas& canvas) { + canvas.drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver); + }); + + ColorTestCanvas canvas; + RenderNodeDrawable drawable(unclippedColorView.get(), &canvas, true); + canvas.drawDrawable(&drawable); + EXPECT_EQ(1, canvas.mDrawCounter); + RenderNodeDrawable drawable2(clippedColorView.get(), &canvas, true); + canvas.drawDrawable(&drawable2); + EXPECT_EQ(2, canvas.mDrawCounter); +} + +TEST(RenderNodeDrawable, renderNode) { + static const int CANVAS_WIDTH = 200; + static const int CANVAS_HEIGHT = 200; + class RenderNodeTestCanvas : public TestCanvasBase { + public: + RenderNodeTestCanvas() : TestCanvasBase(CANVAS_WIDTH, CANVAS_HEIGHT) { + } + void onDrawRect(const SkRect& rect, const SkPaint& paint) override { + switch(mDrawCounter++) { + case 0: + EXPECT_EQ(SkRect::MakeWH(CANVAS_WIDTH, CANVAS_HEIGHT), TestUtils::getClipBounds(this)); + EXPECT_EQ(SK_ColorDKGRAY, paint.getColor()); + break; + case 1: + EXPECT_EQ(SkRect::MakeLTRB(50, 50, 150, 150), TestUtils::getClipBounds(this)); + EXPECT_EQ(SK_ColorWHITE, paint.getColor()); + break; + default: + ADD_FAILURE(); + } + } + }; + + auto child = TestUtils::createSkiaNode(10, 10, 110, 110, + [](RenderProperties& props, SkiaRecordingCanvas& canvas) { + SkPaint paint; + paint.setColor(SK_ColorWHITE); + canvas.drawRect(0, 0, 100, 100, paint); + }); + + auto parent = TestUtils::createSkiaNode(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT, + [&child](RenderProperties& props, SkiaRecordingCanvas& canvas) { + SkPaint paint; + paint.setColor(SK_ColorDKGRAY); + canvas.drawRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT, paint); + + canvas.save(SaveFlags::MatrixClip); + canvas.translate(40, 40); + canvas.drawRenderNode(child.get()); + canvas.restore(); + }); + + RenderNodeTestCanvas canvas; + RenderNodeDrawable drawable(parent.get(), &canvas, true); + canvas.drawDrawable(&drawable); + EXPECT_EQ(2, canvas.mDrawCounter); +} + diff --git a/libs/hwui/tests/unit/SkiaPipelineTests.cpp b/libs/hwui/tests/unit/SkiaPipelineTests.cpp index 17801aff40eaf..9b5fa307ebf71 100644 --- a/libs/hwui/tests/unit/SkiaPipelineTests.cpp +++ b/libs/hwui/tests/unit/SkiaPipelineTests.cpp @@ -26,7 +26,9 @@ #include "renderthread/CanvasContext.h" #include "tests/common/TestUtils.h" #include "SkiaCanvas.h" +#include #include +#include #include using namespace android; @@ -53,44 +55,6 @@ RENDERTHREAD_TEST(SkiaPipeline, renderFrame) { ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorRED); } -RENDERTHREAD_TEST(SkiaPipeline, renderFrameCheckBounds) { - auto backdropRedNode = TestUtils::createSkiaNode(1, 1, 4, 4, - [](RenderProperties& props, SkiaRecordingCanvas& redCanvas) { - redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver); - }); - auto contentGreenNode = TestUtils::createSkiaNode(2, 2, 5, 5, - [](RenderProperties& props, SkiaRecordingCanvas& blueCanvas) { - blueCanvas.drawColor(SK_ColorGREEN, SkBlendMode::kSrcOver); - }); - LayerUpdateQueue layerUpdateQueue; - SkRect dirty = SkRect::MakeLargest(); - std::vector> renderNodes; - renderNodes.push_back(backdropRedNode); //first node is backdrop - renderNodes.push_back(contentGreenNode); //second node is content drawn on top of the backdrop - android::uirenderer::Rect contentDrawBounds(1, 1, 3, 3); - auto pipeline = std::make_unique(renderThread); - auto surface = SkSurface::MakeRasterN32Premul(5, 5); - surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver); - ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE); - //backdropBounds is (1, 1, 3, 3), content clip is (1, 1, 3, 3), content translate is (0, 0) - pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, true, contentDrawBounds, surface); - ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE); - ASSERT_EQ(TestUtils::getColor(surface, 1, 1), SK_ColorRED); - ASSERT_EQ(TestUtils::getColor(surface, 2, 2), SK_ColorGREEN); - ASSERT_EQ(TestUtils::getColor(surface, 3, 3), SK_ColorRED); - ASSERT_EQ(TestUtils::getColor(surface, 4, 4), SK_ColorBLUE); - - surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver); - contentDrawBounds.set(0, 0, 5, 5); - //backdropBounds is (1, 1, 4, 4), content clip is (0, 0, 3, 3), content translate is (1, 1) - pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, true, contentDrawBounds, surface); - ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE); - ASSERT_EQ(TestUtils::getColor(surface, 1, 1), SK_ColorRED); - ASSERT_EQ(TestUtils::getColor(surface, 2, 2), SK_ColorRED); - ASSERT_EQ(TestUtils::getColor(surface, 3, 3), SK_ColorGREEN); - ASSERT_EQ(TestUtils::getColor(surface, 4, 4), SK_ColorBLUE); -} - RENDERTHREAD_TEST(SkiaPipeline, renderFrameCheckOpaque) { auto halfGreenNode = TestUtils::createSkiaNode(0, 0, 2, 2, [](RenderProperties& props, SkiaRecordingCanvas& bottomHalfGreenCanvas) { @@ -192,7 +156,8 @@ RENDERTHREAD_TEST(SkiaPipeline, renderOverdraw) { std::vector> renderNodes; renderNodes.push_back(whiteNode); bool opaque = true; - android::uirenderer::Rect contentDrawBounds(0, 0, 1, 1); + //empty contentDrawBounds is avoiding backdrop/content logic, which would lead to less overdraw + android::uirenderer::Rect contentDrawBounds(0, 0, 0, 0); auto pipeline = std::make_unique(renderThread); auto surface = SkSurface::MakeRasterN32Premul(1, 1); @@ -205,7 +170,9 @@ RENDERTHREAD_TEST(SkiaPipeline, renderOverdraw) { ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorWHITE); // 1 Overdraw, should be blue blended onto white. - renderNodes.push_back(whiteNode); + renderNodes.push_back(whiteNode); //this is the "content" node + renderNodes.push_back(whiteNode); //the "content" node above does not cause an overdraw, because + //it clips the first "background" node pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), (unsigned) 0xffd0d0ff); @@ -229,3 +196,160 @@ RENDERTHREAD_TEST(SkiaPipeline, renderOverdraw) { pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), (unsigned) 0xffff8080); } + +namespace { +template +class DeferLayer : public SkSurface_Base { +public: + DeferLayer(T *canvas) + : SkSurface_Base(canvas->imageInfo(), nullptr) + , mCanvas(canvas) { + } + SkCanvas* onNewCanvas() override { + mCanvas->ref(); + return mCanvas; + } + sk_sp onNewSurface(const SkImageInfo&) override { + return sk_sp(); + } + sk_sp onNewImageSnapshot(SkBudgeted, SkCopyPixelsMode) override { + return sk_sp(); + } + void onCopyOnWrite(ContentChangeMode) override {} + T* mCanvas; +}; +} + +RENDERTHREAD_TEST(SkiaPipeline, deferRenderNodeScene) { + class DeferTestCanvas : public SkCanvas { + public: + DeferTestCanvas() : SkCanvas(800, 600) {} + void onDrawRect(const SkRect& rect, const SkPaint& paint) override { + SkMatrix expected; + switch (mDrawCounter++) { + case 0: + // background - left side + EXPECT_EQ(SkRect::MakeLTRB(600, 100, 700, 500), TestUtils::getClipBounds(this)); + expected.setTranslate(100, 100); + break; + case 1: + // background - top side + EXPECT_EQ(SkRect::MakeLTRB(100, 400, 600, 500), TestUtils::getClipBounds(this)); + expected.setTranslate(100, 100); + break; + case 2: + // content + EXPECT_EQ(SkRect::MakeLTRB(100, 100, 700, 500), TestUtils::getClipBounds(this)); + expected.setTranslate(-50, -50); + break; + case 3: + // overlay + EXPECT_EQ(SkRect::MakeLTRB(0, 0, 800, 600), TestUtils::getClipBounds(this)); + expected.reset(); + break; + default: + ADD_FAILURE() << "Too many rects observed"; + } + EXPECT_EQ(expected, getTotalMatrix()); + } + int mDrawCounter = 0; + }; + + std::vector> nodes; + SkPaint transparentPaint; + transparentPaint.setAlpha(128); + + // backdrop + nodes.push_back(TestUtils::createSkiaNode(100, 100, 700, 500, // 600x400 + [&transparentPaint](RenderProperties& props, SkiaRecordingCanvas& canvas) { + canvas.drawRect(0, 0, 600, 400, transparentPaint); + })); + + // content + android::uirenderer::Rect contentDrawBounds(150, 150, 650, 450); // 500x300 + nodes.push_back(TestUtils::createSkiaNode(0, 0, 800, 600, + [&transparentPaint](RenderProperties& props, SkiaRecordingCanvas& canvas) { + canvas.drawRect(0, 0, 800, 600, transparentPaint); + })); + + // overlay + nodes.push_back(TestUtils::createSkiaNode(0, 0, 800, 600, + [&transparentPaint](RenderProperties& props, SkiaRecordingCanvas& canvas) { + canvas.drawRect(0, 0, 800, 200, transparentPaint); + })); + + LayerUpdateQueue layerUpdateQueue; + SkRect dirty = SkRect::MakeWH(800, 600); + auto pipeline = std::make_unique(renderThread); + sk_sp canvas(new DeferTestCanvas()); + sk_sp surface(new DeferLayer(canvas.get())); + pipeline->renderFrame(layerUpdateQueue, dirty, nodes, true, contentDrawBounds, surface); + EXPECT_EQ(4, canvas->mDrawCounter); +} + +RENDERTHREAD_TEST(SkiaPipeline, clipped) { + static const int CANVAS_WIDTH = 200; + static const int CANVAS_HEIGHT = 200; + class ClippedTestCanvas : public SkCanvas { + public: + ClippedTestCanvas() : SkCanvas(CANVAS_WIDTH, CANVAS_HEIGHT) { + } + void onDrawImage(const SkImage*, SkScalar dx, SkScalar dy, const SkPaint*) override { + EXPECT_EQ(0, mDrawCounter++); + EXPECT_EQ(SkRect::MakeLTRB(10, 20, 30, 40), TestUtils::getClipBounds(this)); + EXPECT_TRUE(getTotalMatrix().isIdentity()); + } + int mDrawCounter = 0; + }; + + std::vector> nodes; + nodes.push_back(TestUtils::createSkiaNode(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT, + [](RenderProperties& props, SkiaRecordingCanvas& canvas) { + sk_sp bitmap(TestUtils::createBitmap(CANVAS_WIDTH, CANVAS_HEIGHT)); + canvas.drawBitmap(*bitmap, 0, 0, nullptr); + })); + + LayerUpdateQueue layerUpdateQueue; + SkRect dirty = SkRect::MakeLTRB(10, 20, 30, 40); + auto pipeline = std::make_unique(renderThread); + sk_sp canvas(new ClippedTestCanvas()); + sk_sp surface(new DeferLayer(canvas.get())); + pipeline->renderFrame(layerUpdateQueue, dirty, nodes, true, + SkRect::MakeWH(CANVAS_WIDTH, CANVAS_HEIGHT), surface); + EXPECT_EQ(1, canvas->mDrawCounter); +} + +RENDERTHREAD_TEST(SkiaPipeline, clip_replace) { + static const int CANVAS_WIDTH = 50; + static const int CANVAS_HEIGHT = 50; + class ClipReplaceTestCanvas : public SkCanvas { + public: + ClipReplaceTestCanvas() : SkCanvas(CANVAS_WIDTH, CANVAS_HEIGHT) { + } + void onDrawPaint(const SkPaint&) { + EXPECT_EQ(0, mDrawCounter++); + //TODO: this unit test is failing on the commented check below, because of a missing + //feature. In Snapshot::applyClip HWUI is intersecting the clip with the clip root, + //even for kReplace_Op clips. We need to implement the same for Skia pipelines. + //EXPECT_EQ(SkRect::MakeLTRB(20, 10, 30, 40), TestUtils::getClipBounds(this)) //got instead 20 0 30 50 + // << "Expect resolved clip to be intersection of viewport clip and clip op"; + } + int mDrawCounter = 0; + }; + + std::vector> nodes; + nodes.push_back(TestUtils::createSkiaNode(20, 20, 30, 30, + [](RenderProperties& props, SkiaRecordingCanvas& canvas) { + canvas.clipRect(0, -20, 10, 30, kReplace_SkClipOp); + canvas.drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver); + })); + + LayerUpdateQueue layerUpdateQueue; + SkRect dirty = SkRect::MakeLTRB(10, 10, 40, 40); + auto pipeline = std::make_unique(renderThread); + sk_sp canvas(new ClipReplaceTestCanvas()); + sk_sp surface(new DeferLayer(canvas.get())); + pipeline->renderFrame(layerUpdateQueue, dirty, nodes, true, + SkRect::MakeWH(CANVAS_WIDTH, CANVAS_HEIGHT), surface); + EXPECT_EQ(1, canvas->mDrawCounter); +} diff --git a/libs/hwui/tests/unit/SkiaRenderPropertiesTests.cpp b/libs/hwui/tests/unit/SkiaRenderPropertiesTests.cpp new file mode 100644 index 0000000000000..ec2efc8d88b66 --- /dev/null +++ b/libs/hwui/tests/unit/SkiaRenderPropertiesTests.cpp @@ -0,0 +1,149 @@ +/* + * 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 +#include + +#include "AnimationContext.h" +#include "DamageAccumulator.h" +#include "IContextFactory.h" +#include "pipeline/skia/SkiaDisplayList.h" +#include "pipeline/skia/SkiaPipeline.h" +#include "pipeline/skia/SkiaRecordingCanvas.h" +#include "renderthread/CanvasContext.h" +#include "tests/common/TestUtils.h" +#include "SkiaCanvas.h" +#include +#include +#include +#include "FatalTestCanvas.h" +#include + +using namespace android; +using namespace android::uirenderer; +using namespace android::uirenderer::renderthread; +using namespace android::uirenderer::skiapipeline; + +namespace { + +static void testProperty(std::function propSetupCallback, + std::function opValidateCallback) { + static const int CANVAS_WIDTH = 100; + static const int CANVAS_HEIGHT = 100; + class PropertyTestCanvas : public TestCanvasBase { + public: + PropertyTestCanvas(std::function callback) + : TestCanvasBase(CANVAS_WIDTH, CANVAS_HEIGHT), mCallback(callback) {} + void onDrawRect(const SkRect& rect, const SkPaint& paint) override { + EXPECT_EQ(mDrawCounter++, 0); + mCallback(*this); + } + void onClipRRect(const SkRRect& rrect, ClipOp op, ClipEdgeStyle style) { + SkCanvas::onClipRRect(rrect, op, style); + } + std::function mCallback; + }; + + auto node = TestUtils::createSkiaNode(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT, + [propSetupCallback](RenderProperties& props, SkiaRecordingCanvas& canvas) { + propSetupCallback(props); + SkPaint paint; + paint.setColor(SK_ColorWHITE); + canvas.drawRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT, paint); + }); + + sk_sp canvas(new PropertyTestCanvas(opValidateCallback)); + RenderNodeDrawable drawable(node.get(), canvas.get(), true); + canvas->drawDrawable(&drawable); + EXPECT_EQ(1, canvas->mDrawCounter); +} + +} + +RENDERTHREAD_TEST(RenderNodeDrawable, renderPropClipping) { + testProperty([](RenderProperties& properties) { + properties.setClipToBounds(true); + properties.setClipBounds(android::uirenderer::Rect(10, 20, 300, 400)); + }, [](const SkCanvas& canvas) { + EXPECT_EQ(SkRect::MakeLTRB(10, 20, 100, 100), TestUtils::getClipBounds(&canvas)) + << "Clip rect should be intersection of node bounds and clip bounds"; + }); +} + +RENDERTHREAD_TEST(RenderNodeDrawable, renderPropRevealClip) { + testProperty([](RenderProperties& properties) { + properties.mutableRevealClip().set(true, 50, 50, 25); + }, [](const SkCanvas& canvas) { + SkClipStack::Iter it(*canvas.getClipStack(), SkClipStack::Iter::kBottom_IterStart); + const SkClipStack::Element *top = it.next(); + ASSERT_NE(nullptr, top); + SkPath clip; + top->asPath(&clip); + SkRect rect; + EXPECT_TRUE(clip.isOval(&rect)); + EXPECT_EQ(SkRect::MakeLTRB(25, 25, 75, 75), rect); + }); +} + +RENDERTHREAD_TEST(RenderNodeDrawable, renderPropOutlineClip) { + testProperty([](RenderProperties& properties) { + properties.mutableOutline().setShouldClip(true); + properties.mutableOutline().setRoundRect(10, 20, 30, 40, 5.0f, 0.5f); + }, [](const SkCanvas& canvas) { + SkClipStack::Iter it(*canvas.getClipStack(), SkClipStack::Iter::kBottom_IterStart); + const SkClipStack::Element *top = it.next(); + ASSERT_NE(nullptr, top); + SkPath clip; + top->asPath(&clip); + SkRRect rrect; + EXPECT_TRUE(clip.isRRect(&rrect)); + EXPECT_EQ(SkRRect::MakeRectXY(SkRect::MakeLTRB(10, 20, 30, 40), 5.0f, 5.0f), rrect); + }); +} + +RENDERTHREAD_TEST(RenderNodeDrawable, renderPropTransform) { + testProperty([](RenderProperties& properties) { + properties.setLeftTopRightBottom(10, 10, 110, 110); + + SkMatrix staticMatrix = SkMatrix::MakeScale(1.2f, 1.2f); + properties.setStaticMatrix(&staticMatrix); + + // ignored, since static overrides animation + SkMatrix animationMatrix = SkMatrix::MakeTrans(15, 15); + properties.setAnimationMatrix(&animationMatrix); + + properties.setTranslationX(10); + properties.setTranslationY(20); + properties.setScaleX(0.5f); + properties.setScaleY(0.7f); + }, [](const SkCanvas& canvas) { + Matrix4 matrix; + matrix.loadTranslate(10, 10, 0); // left, top + matrix.scale(1.2f, 1.2f, 1); // static matrix + // ignore animation matrix, since static overrides it + + // translation xy + matrix.translate(10, 20); + + // scale xy (from default pivot - center) + matrix.translate(50, 50); + matrix.scale(0.5f, 0.7f, 1); + matrix.translate(-50, -50); + Matrix4 actual(canvas.getTotalMatrix()); + EXPECT_MATRIX_APPROX_EQ(matrix, actual) + << "Op draw matrix must match expected combination of transformation properties"; + }); +}