| /* |
| * Copyright (C) 2020 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include <gtest/gtest.h> |
| |
| #include <canvas/CanvasOpBuffer.h> |
| #include <canvas/CanvasOps.h> |
| #include <canvas/CanvasOpRasterizer.h> |
| |
| #include <tests/common/CallCountingCanvas.h> |
| |
| #include "SkColor.h" |
| #include "pipeline/skia/AnimatedDrawables.h" |
| |
| using namespace android; |
| using namespace android::uirenderer; |
| using namespace android::uirenderer::test; |
| |
| // We lazy |
| using Op = CanvasOpType; |
| |
| enum MockTypes { |
| Lifecycle, |
| COUNT |
| }; |
| |
| template<MockTypes T> |
| struct MockOp; |
| |
| template<MockTypes T> |
| struct MockOpContainer { |
| OpBufferItemHeader<MockTypes> header; |
| MockOp<T> impl; |
| }; |
| |
| struct LifecycleTracker { |
| int ctor_count = 0; |
| int dtor_count = 0; |
| |
| int alive() { return ctor_count - dtor_count; } |
| }; |
| |
| template<> |
| struct MockOp<MockTypes::Lifecycle> { |
| MockOp() = delete; |
| void operator=(const MockOp&) = delete; |
| |
| MockOp(LifecycleTracker* tracker) : tracker(tracker) { |
| tracker->ctor_count += 1; |
| } |
| |
| MockOp(const MockOp& other) { |
| tracker = other.tracker; |
| tracker->ctor_count += 1; |
| } |
| |
| ~MockOp() { |
| tracker->dtor_count += 1; |
| } |
| |
| LifecycleTracker* tracker = nullptr; |
| }; |
| |
| using MockBuffer = OpBuffer<MockTypes, MockOpContainer>; |
| |
| template<typename T> |
| static int countItems(const T& t) { |
| int count = 0; |
| t.for_each([&](auto i) { |
| count++; |
| }); |
| return count; |
| } |
| |
| TEST(CanvasOp, lifecycleCheck) { |
| LifecycleTracker tracker; |
| { |
| MockBuffer buffer; |
| buffer.push_container(MockOpContainer<MockTypes::Lifecycle> { |
| .impl = MockOp<MockTypes::Lifecycle>{&tracker} |
| }); |
| EXPECT_EQ(tracker.alive(), 1); |
| buffer.clear(); |
| EXPECT_EQ(tracker.alive(), 0); |
| } |
| EXPECT_EQ(tracker.alive(), 0); |
| } |
| |
| TEST(CanvasOp, lifecycleCheckMove) { |
| LifecycleTracker tracker; |
| { |
| MockBuffer buffer; |
| buffer.push_container(MockOpContainer<MockTypes::Lifecycle> { |
| .impl = MockOp<MockTypes::Lifecycle>{&tracker} |
| }); |
| EXPECT_EQ(tracker.alive(), 1); |
| { |
| MockBuffer other(std::move(buffer)); |
| EXPECT_EQ(tracker.alive(), 1); |
| EXPECT_EQ(buffer.size(), 0); |
| EXPECT_GT(other.size(), 0); |
| EXPECT_EQ(1, countItems(other)); |
| EXPECT_EQ(0, countItems(buffer)); |
| |
| other.push_container(MockOpContainer<MockTypes::Lifecycle> { |
| .impl = MockOp<MockTypes::Lifecycle>{&tracker} |
| }); |
| |
| EXPECT_EQ(2, countItems(other)); |
| EXPECT_EQ(2, tracker.alive()); |
| |
| buffer.push_container(MockOpContainer<MockTypes::Lifecycle> { |
| .impl = MockOp<MockTypes::Lifecycle>{&tracker} |
| }); |
| EXPECT_EQ(1, countItems(buffer)); |
| EXPECT_EQ(3, tracker.alive()); |
| |
| buffer = std::move(other); |
| EXPECT_EQ(2, countItems(buffer)); |
| EXPECT_EQ(2, tracker.alive()); |
| } |
| EXPECT_EQ(2, countItems(buffer)); |
| EXPECT_EQ(2, tracker.alive()); |
| buffer.clear(); |
| EXPECT_EQ(0, countItems(buffer)); |
| EXPECT_EQ(0, tracker.alive()); |
| } |
| EXPECT_EQ(tracker.alive(), 0); |
| } |
| |
| TEST(CanvasOp, simplePush) { |
| CanvasOpBuffer buffer; |
| EXPECT_EQ(buffer.size(), 0); |
| buffer.push<Op::Save>({}); |
| buffer.push<Op::Save>({}); |
| buffer.push<Op::Restore>({}); |
| EXPECT_GT(buffer.size(), 0); |
| |
| int saveCount = 0; |
| int restoreCount = 0; |
| int otherCount = 0; |
| |
| buffer.for_each([&](auto op) { |
| switch (op->type()) { |
| case Op::Save: |
| saveCount++; |
| break; |
| case Op::Restore: |
| restoreCount++; |
| break; |
| default: |
| otherCount++; |
| break; |
| } |
| }); |
| |
| EXPECT_EQ(saveCount, 2); |
| EXPECT_EQ(restoreCount, 1); |
| EXPECT_EQ(otherCount, 0); |
| |
| buffer.clear(); |
| int itemCount = 0; |
| buffer.for_each([&](auto op) { |
| itemCount++; |
| }); |
| EXPECT_EQ(itemCount, 0); |
| buffer.resize(0); |
| EXPECT_EQ(buffer.size(), 0); |
| } |
| |
| TEST(CanvasOp, simpleDrawPaint) { |
| CanvasOpBuffer buffer; |
| EXPECT_EQ(buffer.size(), 0); |
| buffer.push(CanvasOp<Op::DrawColor> { |
| .color = SkColor4f{1, 1, 1, 1}, |
| .mode = SkBlendMode::kSrcIn |
| }); |
| |
| CallCountingCanvas canvas; |
| EXPECT_EQ(0, canvas.sumTotalDrawCalls()); |
| rasterizeCanvasBuffer(buffer, &canvas); |
| EXPECT_EQ(1, canvas.drawPaintCount); |
| EXPECT_EQ(1, canvas.sumTotalDrawCalls()); |
| } |
| |
| TEST(CanvasOp, simpleDrawRect) { |
| CanvasOpBuffer buffer; |
| EXPECT_EQ(buffer.size(), 0); |
| buffer.push(CanvasOp<Op::DrawRect> { |
| .paint = SkPaint{}, |
| .rect = SkRect::MakeEmpty() |
| }); |
| |
| CallCountingCanvas canvas; |
| EXPECT_EQ(0, canvas.sumTotalDrawCalls()); |
| rasterizeCanvasBuffer(buffer, &canvas); |
| EXPECT_EQ(1, canvas.drawRectCount); |
| EXPECT_EQ(1, canvas.sumTotalDrawCalls()); |
| } |
| |
| TEST(CanvasOp, simpleDrawRoundRect) { |
| CanvasOpBuffer buffer; |
| EXPECT_EQ(buffer.size(), 0); |
| buffer.push(CanvasOp<Op::DrawRoundRect> { |
| .paint = SkPaint{}, |
| .rect = SkRect::MakeEmpty(), |
| .rx = 10, |
| .ry = 10 |
| }); |
| |
| CallCountingCanvas canvas; |
| EXPECT_EQ(0, canvas.sumTotalDrawCalls()); |
| rasterizeCanvasBuffer(buffer, &canvas); |
| EXPECT_EQ(1, canvas.drawRRectCount); |
| EXPECT_EQ(1, canvas.sumTotalDrawCalls()); |
| } |
| |
| TEST(CanvasOp, simpleDrawCircle) { |
| CanvasOpBuffer buffer; |
| EXPECT_EQ(buffer.size(), 0); |
| buffer.push(CanvasOp<Op::DrawCircle> { |
| .cx = 5, |
| .cy = 7, |
| .radius = 10, |
| .paint = SkPaint{} |
| }); |
| |
| CallCountingCanvas canvas; |
| EXPECT_EQ(0, canvas.sumTotalDrawCalls()); |
| rasterizeCanvasBuffer(buffer, &canvas); |
| EXPECT_EQ(1, canvas.drawOvalCount); |
| EXPECT_EQ(1, canvas.sumTotalDrawCalls()); |
| } |
| |
| TEST(CanvasOp, simpleDrawOval) { |
| CanvasOpBuffer buffer; |
| EXPECT_EQ(buffer.size(), 0); |
| buffer.push(CanvasOp<Op::DrawOval> { |
| .oval = SkRect::MakeEmpty(), |
| .paint = SkPaint{} |
| }); |
| |
| CallCountingCanvas canvas; |
| EXPECT_EQ(0, canvas.sumTotalDrawCalls()); |
| rasterizeCanvasBuffer(buffer, &canvas); |
| EXPECT_EQ(1, canvas.drawOvalCount); |
| EXPECT_EQ(1, canvas.sumTotalDrawCalls()); |
| } |
| |
| TEST(CanvasOp, simpleDrawArc) { |
| CanvasOpBuffer buffer; |
| EXPECT_EQ(buffer.size(), 0); |
| buffer.push(CanvasOp<Op::DrawArc> { |
| .oval = SkRect::MakeWH(100, 100), |
| .startAngle = 120, |
| .sweepAngle = 70, |
| .useCenter = true, |
| .paint = SkPaint{} |
| }); |
| |
| CallCountingCanvas canvas; |
| EXPECT_EQ(0, canvas.sumTotalDrawCalls()); |
| rasterizeCanvasBuffer(buffer, &canvas); |
| EXPECT_EQ(1, canvas.drawArcCount); |
| EXPECT_EQ(1, canvas.sumTotalDrawCalls()); |
| } |
| |
| TEST(CanvasOp, simpleDrawRoundRectProperty) { |
| CanvasOpBuffer buffer; |
| EXPECT_EQ(buffer.size(), 0); |
| |
| auto left = sp<CanvasPropertyPrimitive>(new uirenderer::CanvasPropertyPrimitive(1)); |
| auto top = sp<CanvasPropertyPrimitive>(new uirenderer::CanvasPropertyPrimitive(2)); |
| auto right = sp<CanvasPropertyPrimitive>(new uirenderer::CanvasPropertyPrimitive(3)); |
| auto bottom = sp<CanvasPropertyPrimitive>(new uirenderer::CanvasPropertyPrimitive(4)); |
| auto radiusX = sp<CanvasPropertyPrimitive>(new uirenderer::CanvasPropertyPrimitive(5)); |
| auto radiusY = sp<CanvasPropertyPrimitive>(new uirenderer::CanvasPropertyPrimitive(6)); |
| auto propertyPaint = |
| sp<uirenderer::CanvasPropertyPaint>(new uirenderer::CanvasPropertyPaint(SkPaint{})); |
| |
| buffer.push(CanvasOp<Op::DrawRoundRectProperty> { |
| .left = left, |
| .top = top, |
| .right = right, |
| .bottom = bottom, |
| .rx = radiusX, |
| .ry = radiusY, |
| .paint = propertyPaint |
| }); |
| |
| CallCountingCanvas canvas; |
| EXPECT_EQ(0, canvas.sumTotalDrawCalls()); |
| rasterizeCanvasBuffer(buffer, &canvas); |
| EXPECT_EQ(1, canvas.drawRRectCount); |
| EXPECT_EQ(1, canvas.sumTotalDrawCalls()); |
| } |
| |
| TEST(CanvasOp, simpleDrawCircleProperty) { |
| CanvasOpBuffer buffer; |
| EXPECT_EQ(buffer.size(), 0); |
| |
| auto x = sp<CanvasPropertyPrimitive>(new uirenderer::CanvasPropertyPrimitive(1)); |
| auto y = sp<CanvasPropertyPrimitive>(new uirenderer::CanvasPropertyPrimitive(2)); |
| auto radius = sp<CanvasPropertyPrimitive>(new uirenderer::CanvasPropertyPrimitive(5)); |
| auto propertyPaint = |
| sp<uirenderer::CanvasPropertyPaint>(new uirenderer::CanvasPropertyPaint(SkPaint{})); |
| |
| buffer.push(CanvasOp<Op::DrawCircleProperty> { |
| .x = x, |
| .y = y, |
| .radius = radius, |
| .paint = propertyPaint |
| }); |
| |
| CallCountingCanvas canvas; |
| EXPECT_EQ(0, canvas.sumTotalDrawCalls()); |
| rasterizeCanvasBuffer(buffer, &canvas); |
| EXPECT_EQ(1, canvas.drawOvalCount); |
| EXPECT_EQ(1, canvas.sumTotalDrawCalls()); |
| } |
| |
| TEST(CanvasOp, immediateRendering) { |
| auto canvas = std::make_shared<CallCountingCanvas>(); |
| |
| EXPECT_EQ(0, canvas->sumTotalDrawCalls()); |
| ImmediateModeRasterizer rasterizer{canvas}; |
| auto op = CanvasOp<Op::DrawRect> { |
| .paint = SkPaint{}, |
| .rect = SkRect::MakeEmpty() |
| }; |
| EXPECT_TRUE(CanvasOpTraits::can_draw<decltype(op)>); |
| rasterizer.draw(op); |
| EXPECT_EQ(1, canvas->drawRectCount); |
| EXPECT_EQ(1, canvas->sumTotalDrawCalls()); |
| } |