blob: 3ded540c31524ffab68b4232a42b563b6bbed41c [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <VectorDrawable.h>
#include <gtest/gtest.h>
#include <SkBlendMode.h>
#include <SkClipStack.h>
#include <SkSurface_Base.h>
#include <string.h>
#include "AnimationContext.h"
#include "DamageAccumulator.h"
#include "IContextFactory.h"
#include "hwui/Paint.h"
#include "SkiaCanvas.h"
#include "pipeline/skia/SkiaDisplayList.h"
#include "pipeline/skia/SkiaOpenGLPipeline.h"
#include "pipeline/skia/SkiaRecordingCanvas.h"
#include "pipeline/skia/SkiaUtils.h"
#include "renderthread/CanvasContext.h"
#include "tests/common/TestContext.h"
#include "tests/common/TestUtils.h"
#include <gui/BufferItemConsumer.h>
#include <gui/Surface.h>
using namespace android;
using namespace android::uirenderer;
using namespace android::uirenderer::renderthread;
using namespace android::uirenderer::skiapipeline;
RENDERTHREAD_TEST(SkiaPipeline, renderFrame) {
auto redNode = TestUtils::createSkiaNode(
0, 0, 1, 1, [](RenderProperties& props, SkiaRecordingCanvas& redCanvas) {
redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver);
});
LayerUpdateQueue layerUpdateQueue;
SkRect dirty = SkRectMakeLargest();
std::vector<sp<RenderNode>> renderNodes;
renderNodes.push_back(redNode);
bool opaque = true;
android::uirenderer::Rect contentDrawBounds(0, 0, 1, 1);
auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(1, 1));
surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface,
SkMatrix::I());
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorRED);
}
RENDERTHREAD_TEST(SkiaPipeline, renderFrameCheckOpaque) {
auto halfGreenNode = TestUtils::createSkiaNode(
0, 0, 2, 2, [](RenderProperties& props, SkiaRecordingCanvas& bottomHalfGreenCanvas) {
Paint greenPaint;
greenPaint.setColor(SK_ColorGREEN);
greenPaint.setStyle(SkPaint::kFill_Style);
bottomHalfGreenCanvas.drawRect(0, 1, 2, 2, greenPaint);
});
LayerUpdateQueue layerUpdateQueue;
SkRect dirty = SkRectMakeLargest();
std::vector<sp<RenderNode>> renderNodes;
renderNodes.push_back(halfGreenNode);
android::uirenderer::Rect contentDrawBounds(0, 0, 2, 2);
auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(2, 2));
surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, true, contentDrawBounds, surface,
SkMatrix::I());
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
ASSERT_EQ(TestUtils::getColor(surface, 0, 1), SK_ColorGREEN);
pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, false, contentDrawBounds, surface,
SkMatrix::I());
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), (unsigned int)SK_ColorTRANSPARENT);
ASSERT_EQ(TestUtils::getColor(surface, 0, 1), SK_ColorGREEN);
}
RENDERTHREAD_TEST(SkiaPipeline, renderFrameCheckDirtyRect) {
auto redNode = TestUtils::createSkiaNode(
0, 0, 2, 2, [](RenderProperties& props, SkiaRecordingCanvas& redCanvas) {
redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver);
});
LayerUpdateQueue layerUpdateQueue;
SkRect dirty = SkRect::MakeXYWH(0, 1, 2, 1);
std::vector<sp<RenderNode>> renderNodes;
renderNodes.push_back(redNode);
android::uirenderer::Rect contentDrawBounds(0, 0, 2, 2);
auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(2, 2));
surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, true, contentDrawBounds, surface,
SkMatrix::I());
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
ASSERT_EQ(TestUtils::getColor(surface, 1, 0), SK_ColorBLUE);
ASSERT_EQ(TestUtils::getColor(surface, 0, 1), SK_ColorRED);
ASSERT_EQ(TestUtils::getColor(surface, 1, 1), SK_ColorRED);
}
RENDERTHREAD_TEST(SkiaPipeline, renderLayer) {
auto redNode = TestUtils::createSkiaNode(
0, 0, 1, 1, [](RenderProperties& props, SkiaRecordingCanvas& redCanvas) {
redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver);
});
auto surfaceLayer1 = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(1, 1));
surfaceLayer1->getCanvas()->drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver);
ASSERT_EQ(TestUtils::getColor(surfaceLayer1, 0, 0), SK_ColorWHITE);
redNode->setLayerSurface(surfaceLayer1);
// create a 2nd 2x2 layer and add it to the queue as well.
// make the layer's dirty area one half of the layer and verify only the dirty half is updated.
auto blueNode = TestUtils::createSkiaNode(
0, 0, 2, 2, [](RenderProperties& props, SkiaRecordingCanvas& blueCanvas) {
blueCanvas.drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
});
auto surfaceLayer2 = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(2, 2));
surfaceLayer2->getCanvas()->drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver);
ASSERT_EQ(TestUtils::getColor(surfaceLayer2, 0, 0), SK_ColorWHITE);
blueNode->setLayerSurface(surfaceLayer2);
// attach both layers to the update queue
LayerUpdateQueue layerUpdateQueue;
SkRect dirty = SkRectMakeLargest();
layerUpdateQueue.enqueueLayerWithDamage(redNode.get(), dirty);
layerUpdateQueue.enqueueLayerWithDamage(blueNode.get(), SkRect::MakeWH(2, 1));
ASSERT_EQ(layerUpdateQueue.entries().size(), 2UL);
bool opaque = true;
LightGeometry lightGeometry;
lightGeometry.radius = 1.0f;
lightGeometry.center = {0.0f, 0.0f, 0.0f};
LightInfo lightInfo;
auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
pipeline->renderLayers(lightGeometry, &layerUpdateQueue, opaque, lightInfo);
ASSERT_EQ(TestUtils::getColor(surfaceLayer1, 0, 0), SK_ColorRED);
ASSERT_EQ(TestUtils::getColor(surfaceLayer2, 0, 0), SK_ColorBLUE);
ASSERT_EQ(TestUtils::getColor(surfaceLayer2, 0, 1), SK_ColorWHITE);
ASSERT_TRUE(layerUpdateQueue.entries().empty());
redNode->setLayerSurface(sk_sp<SkSurface>());
blueNode->setLayerSurface(sk_sp<SkSurface>());
}
RENDERTHREAD_TEST(SkiaPipeline, renderOverdraw) {
ScopedProperty<bool> prop(Properties::debugOverdraw, true);
auto whiteNode = TestUtils::createSkiaNode(
0, 0, 1, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
canvas.drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver);
});
LayerUpdateQueue layerUpdateQueue;
SkRect dirty = SkRect::MakeXYWH(0, 0, 1, 1);
std::vector<sp<RenderNode>> renderNodes;
renderNodes.push_back(whiteNode);
bool opaque = true;
// 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<SkiaOpenGLPipeline>(renderThread);
auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(1, 1));
// Initialize the canvas to blue.
surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
// Single draw, should be white.
pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface,
SkMatrix::I());
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorWHITE);
// 1 Overdraw, should be blue blended onto white.
renderNodes.push_back(whiteNode);
pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface,
SkMatrix::I());
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), (unsigned)0xffd0d0ff);
// 2 Overdraw, should be green blended onto white
renderNodes.push_back(whiteNode);
pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface,
SkMatrix::I());
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), (unsigned)0xffd0ffd0);
// 3 Overdraw, should be pink blended onto white.
renderNodes.push_back(whiteNode);
pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface,
SkMatrix::I());
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), (unsigned)0xffffc0c0);
// 4 Overdraw, should be red blended onto white.
renderNodes.push_back(whiteNode);
pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface,
SkMatrix::I());
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), (unsigned)0xffff8080);
// 5 Overdraw, should be red blended onto white.
renderNodes.push_back(whiteNode);
pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface,
SkMatrix::I());
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), (unsigned)0xffff8080);
}
namespace {
template <typename T>
class DeferLayer : public SkSurface_Base {
public:
DeferLayer() : SkSurface_Base(T().imageInfo(), nullptr) {}
virtual ~DeferLayer() {}
SkCanvas* onNewCanvas() override { return new T(); }
sk_sp<SkSurface> onNewSurface(const SkImageInfo&) override { return nullptr; }
sk_sp<SkImage> onNewImageSnapshot(const SkIRect* bounds) override { return nullptr; }
T* canvas() { return static_cast<T*>(getCanvas()); }
bool onCopyOnWrite(ContentChangeMode) override { return true; }
void onWritePixels(const SkPixmap&, int x, int y) override {}
};
}
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<sp<RenderNode>> nodes;
Paint 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<SkiaOpenGLPipeline>(renderThread);
sk_sp<DeferLayer<DeferTestCanvas>> surface(new DeferLayer<DeferTestCanvas>());
pipeline->renderFrame(layerUpdateQueue, dirty, nodes, true, contentDrawBounds, surface,
SkMatrix::I());
EXPECT_EQ(4, surface->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 onDrawImage2(const SkImage*, SkScalar dx, SkScalar dy, const SkSamplingOptions&,
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<sp<RenderNode>> nodes;
nodes.push_back(TestUtils::createSkiaNode(
0, 0, CANVAS_WIDTH, CANVAS_HEIGHT,
[](RenderProperties& props, SkiaRecordingCanvas& canvas) {
sk_sp<Bitmap> 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<SkiaOpenGLPipeline>(renderThread);
sk_sp<DeferLayer<ClippedTestCanvas>> surface(new DeferLayer<ClippedTestCanvas>());
pipeline->renderFrame(layerUpdateQueue, dirty, nodes, true,
SkRect::MakeWH(CANVAS_WIDTH, CANVAS_HEIGHT), surface, SkMatrix::I());
EXPECT_EQ(1, surface->canvas()->mDrawCounter);
}
// Test renderFrame with a dirty clip and a pre-transform matrix.
RENDERTHREAD_TEST(SkiaPipeline, clipped_rotated) {
static const int CANVAS_WIDTH = 200;
static const int CANVAS_HEIGHT = 100;
static const SkMatrix rotateMatrix = SkMatrix::MakeAll(0, -1, CANVAS_HEIGHT, 1, 0, 0, 0, 0, 1);
static const SkRect dirty = SkRect::MakeLTRB(10, 20, 20, 40);
class ClippedTestCanvas : public SkCanvas {
public:
ClippedTestCanvas() : SkCanvas(CANVAS_WIDTH, CANVAS_HEIGHT) {}
void onDrawImage2(const SkImage*, SkScalar dx, SkScalar dy, const SkSamplingOptions&,
const SkPaint*) override {
EXPECT_EQ(0, mDrawCounter++);
// Expect clip to be rotated.
EXPECT_EQ(SkRect::MakeLTRB(CANVAS_HEIGHT - dirty.fTop - dirty.height(), dirty.fLeft,
CANVAS_HEIGHT - dirty.fTop, dirty.fLeft + dirty.width()),
TestUtils::getClipBounds(this));
EXPECT_EQ(rotateMatrix, getTotalMatrix());
}
int mDrawCounter = 0;
};
std::vector<sp<RenderNode>> nodes;
nodes.push_back(TestUtils::createSkiaNode(
0, 0, CANVAS_WIDTH, CANVAS_HEIGHT,
[](RenderProperties& props, SkiaRecordingCanvas& canvas) {
sk_sp<Bitmap> bitmap(TestUtils::createBitmap(CANVAS_WIDTH, CANVAS_HEIGHT));
canvas.drawBitmap(*bitmap, 0, 0, nullptr);
}));
LayerUpdateQueue layerUpdateQueue;
auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
sk_sp<DeferLayer<ClippedTestCanvas>> surface(new DeferLayer<ClippedTestCanvas>());
pipeline->renderFrame(layerUpdateQueue, dirty, nodes, true,
SkRect::MakeWH(CANVAS_WIDTH, CANVAS_HEIGHT), surface, rotateMatrix);
EXPECT_EQ(1, surface->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++);
EXPECT_EQ(SkRect::MakeLTRB(20, 10, 30, 40), TestUtils::getClipBounds(this))
<< "Expect resolved clip to be intersection of viewport clip and clip op";
}
int mDrawCounter = 0;
};
std::vector<sp<RenderNode>> nodes;
nodes.push_back(TestUtils::createSkiaNode(
20, 20, 30, 30, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
canvas.replaceClipRect_deprecated(0, -20, 10, 30);
canvas.drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver);
}));
LayerUpdateQueue layerUpdateQueue;
SkRect dirty = SkRect::MakeLTRB(10, 10, 40, 40);
auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
sk_sp<DeferLayer<ClipReplaceTestCanvas>> surface(new DeferLayer<ClipReplaceTestCanvas>());
pipeline->renderFrame(layerUpdateQueue, dirty, nodes, true,
SkRect::MakeWH(CANVAS_WIDTH, CANVAS_HEIGHT), surface, SkMatrix::I());
EXPECT_EQ(1, surface->canvas()->mDrawCounter);
}
RENDERTHREAD_TEST(SkiaPipeline, context_lost) {
test::TestContext context;
auto surface = context.surface();
auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
EXPECT_FALSE(pipeline->isSurfaceReady());
EXPECT_TRUE(pipeline->setSurface(surface.get(), SwapBehavior::kSwap_default));
EXPECT_TRUE(pipeline->isSurfaceReady());
renderThread.destroyRenderingContext();
EXPECT_FALSE(pipeline->isSurfaceReady());
pipeline->makeCurrent();
EXPECT_TRUE(pipeline->isSurfaceReady());
}
RENDERTHREAD_TEST(SkiaPipeline, pictureCallback) {
// create a pipeline and add a picture callback
auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
int callbackCount = 0;
pipeline->setPictureCapturedCallback(
[&callbackCount](sk_sp<SkPicture>&& picture) { callbackCount += 1; });
// create basic red frame and render it
auto redNode = TestUtils::createSkiaNode(
0, 0, 1, 1, [](RenderProperties& props, SkiaRecordingCanvas& redCanvas) {
redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver);
});
LayerUpdateQueue layerUpdateQueue;
SkRect dirty = SkRectMakeLargest();
std::vector<sp<RenderNode>> renderNodes;
renderNodes.push_back(redNode);
bool opaque = true;
android::uirenderer::Rect contentDrawBounds(0, 0, 1, 1);
auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(1, 1));
pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface,
SkMatrix::I());
// verify the callback was called
EXPECT_EQ(1, callbackCount);
// render a second frame and check the callback count
pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface,
SkMatrix::I());
EXPECT_EQ(2, callbackCount);
// unset the callback, render another frame, check callback was not invoked
pipeline->setPictureCapturedCallback(nullptr);
pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface,
SkMatrix::I());
EXPECT_EQ(2, callbackCount);
}