diff options
author | 2020-11-16 12:35:02 -0500 | |
---|---|---|
committer | 2020-11-30 13:14:46 -0500 | |
commit | dc95f1013eb80e382ddaef4f168205a4ebdd0eb6 (patch) | |
tree | 86b2937ebc94882ce4dad8405becdcd842be706b | |
parent | be41f05db49ef724da8fbc6eefeaad310e1be3fc (diff) |
Introduce CanvasFrontend
Add a front-end to convert between "stateful" canvas
and "semi-statelss" CanvasOps. Handles tracking
save/restores, clips, and transforms, and injects
the transform on all draw ops.
Test: CanvasFrontend unit tests
Change-Id: Ifbcd14600690f10717047c50954ab54b4fc27aee
-rw-r--r-- | libs/hwui/Android.bp | 2 | ||||
-rw-r--r-- | libs/hwui/SaveFlags.h | 36 | ||||
-rw-r--r-- | libs/hwui/canvas/CanvasFrontend.cpp | 117 | ||||
-rw-r--r-- | libs/hwui/canvas/CanvasFrontend.h | 200 | ||||
-rw-r--r-- | libs/hwui/canvas/CanvasOpRecorder.cpp | 22 | ||||
-rw-r--r-- | libs/hwui/canvas/CanvasOpRecorder.h | 38 | ||||
-rw-r--r-- | libs/hwui/hwui/Canvas.h | 17 | ||||
-rw-r--r-- | libs/hwui/tests/unit/CanvasFrontendTests.cpp | 213 | ||||
-rw-r--r-- | libs/hwui/tests/unit/CanvasOpTests.cpp | 48 |
9 files changed, 677 insertions, 16 deletions
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index 4ed5457a2a7f..cd53217d2924 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -429,6 +429,7 @@ cc_defaults { whole_static_libs: ["libskia"], srcs: [ + "canvas/CanvasFrontend.cpp", "canvas/CanvasOpBuffer.cpp", "canvas/CanvasOpRasterizer.cpp", "pipeline/skia/SkiaDisplayList.cpp", @@ -607,6 +608,7 @@ cc_test { "tests/unit/CacheManagerTests.cpp", "tests/unit/CanvasContextTests.cpp", "tests/unit/CanvasOpTests.cpp", + "tests/unit/CanvasFrontendTests.cpp", "tests/unit/CommonPoolTests.cpp", "tests/unit/DamageAccumulatorTests.cpp", "tests/unit/DeferredLayerUpdaterTests.cpp", diff --git a/libs/hwui/SaveFlags.h b/libs/hwui/SaveFlags.h new file mode 100644 index 000000000000..f3579a8e9f19 --- /dev/null +++ b/libs/hwui/SaveFlags.h @@ -0,0 +1,36 @@ +/* + * 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. + */ + +#pragma once + +#include <inttypes.h> + +// TODO: Move this to an enum class +namespace android::SaveFlags { + +// These must match the corresponding Canvas API constants. +enum { + Matrix = 0x01, + Clip = 0x02, + HasAlphaLayer = 0x04, + ClipToLayer = 0x10, + + // Helper constant + MatrixClip = Matrix | Clip, +}; +typedef uint32_t Flags; + +} // namespace android::SaveFlags diff --git a/libs/hwui/canvas/CanvasFrontend.cpp b/libs/hwui/canvas/CanvasFrontend.cpp new file mode 100644 index 000000000000..2c839b0ffc15 --- /dev/null +++ b/libs/hwui/canvas/CanvasFrontend.cpp @@ -0,0 +1,117 @@ +/* + * 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 "CanvasFrontend.h" +#include "CanvasOps.h" +#include "CanvasOpBuffer.h" + +namespace android::uirenderer { + +CanvasStateHelper::CanvasStateHelper(int width, int height) { + mInitialBounds = SkIRect::MakeWH(width, height); + mSaveStack.emplace_back(); + mClipStack.emplace_back().setRect(mInitialBounds); + mTransformStack.emplace_back(); + mCurrentClipIndex = 0; + mCurrentTransformIndex = 0; +} + +bool CanvasStateHelper::internalSave(SaveEntry saveEntry) { + mSaveStack.push_back(saveEntry); + if (saveEntry.matrix) { + // We need to push before accessing transform() to ensure the reference doesn't move + // across vector resizes + mTransformStack.emplace_back() = transform(); + mCurrentTransformIndex += 1; + } + if (saveEntry.clip) { + // We need to push before accessing clip() to ensure the reference doesn't move + // across vector resizes + mClipStack.emplace_back() = clip(); + mCurrentClipIndex += 1; + return true; + } + return false; +} + +// Assert that the cast from SkClipOp to SkRegion::Op is valid +static_assert(static_cast<int>(SkClipOp::kDifference) == SkRegion::Op::kDifference_Op); +static_assert(static_cast<int>(SkClipOp::kIntersect) == SkRegion::Op::kIntersect_Op); +static_assert(static_cast<int>(SkClipOp::kUnion_deprecated) == SkRegion::Op::kUnion_Op); +static_assert(static_cast<int>(SkClipOp::kXOR_deprecated) == SkRegion::Op::kXOR_Op); +static_assert(static_cast<int>(SkClipOp::kReverseDifference_deprecated) == SkRegion::Op::kReverseDifference_Op); +static_assert(static_cast<int>(SkClipOp::kReplace_deprecated) == SkRegion::Op::kReplace_Op); + +void CanvasStateHelper::internalClipRect(const SkRect& rect, SkClipOp op) { + clip().opRect(rect, transform(), mInitialBounds, (SkRegion::Op)op, false); +} + +void CanvasStateHelper::internalClipPath(const SkPath& path, SkClipOp op) { + clip().opPath(path, transform(), mInitialBounds, (SkRegion::Op)op, true); +} + +bool CanvasStateHelper::internalRestore() { + // Prevent underflows + if (saveCount() <= 1) { + return false; + } + + SaveEntry entry = mSaveStack[mSaveStack.size() - 1]; + mSaveStack.pop_back(); + bool needsRestorePropagation = entry.layer; + if (entry.matrix) { + mTransformStack.pop_back(); + mCurrentTransformIndex -= 1; + } + if (entry.clip) { + // We need to push before accessing clip() to ensure the reference doesn't move + // across vector resizes + mClipStack.pop_back(); + mCurrentClipIndex -= 1; + needsRestorePropagation = true; + } + return needsRestorePropagation; +} + +SkRect CanvasStateHelper::getClipBounds() const { + SkIRect ibounds = clip().getBounds(); + + if (ibounds.isEmpty()) { + return SkRect::MakeEmpty(); + } + + SkMatrix inverse; + // if we can't invert the CTM, we can't return local clip bounds + if (!transform().invert(&inverse)) { + return SkRect::MakeEmpty(); + } + + SkRect ret = SkRect::MakeEmpty(); + inverse.mapRect(&ret, SkRect::Make(ibounds)); + return ret; +} + +bool CanvasStateHelper::quickRejectRect(float left, float top, float right, float bottom) const { + // TODO: Implement + return false; +} + +bool CanvasStateHelper::quickRejectPath(const SkPath& path) const { + // TODO: Implement + return false; +} + +} // namespace android::uirenderer diff --git a/libs/hwui/canvas/CanvasFrontend.h b/libs/hwui/canvas/CanvasFrontend.h new file mode 100644 index 000000000000..5fccccb0bb43 --- /dev/null +++ b/libs/hwui/canvas/CanvasFrontend.h @@ -0,0 +1,200 @@ +/* + * 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. + */ + +#pragma once + +// TODO: Can we get the dependencies scoped down more? +#include "CanvasOps.h" +#include "CanvasOpBuffer.h" +#include <SaveFlags.h> + +#include <SkRasterClip.h> +#include <ui/FatVector.h> + +#include <optional> + +namespace android::uirenderer { + +// Exists to avoid forcing all this common logic into the templated class +class CanvasStateHelper { +protected: + CanvasStateHelper(int width, int height); + ~CanvasStateHelper() = default; + + struct SaveEntry { + bool clip : 1 = false; + bool matrix : 1 = false; + bool layer : 1 = false; + }; + + constexpr SaveEntry saveEntryForLayer() { + return { + .clip = true, + .matrix = true, + .layer = true, + }; + } + + constexpr SaveEntry flagsToSaveEntry(SaveFlags::Flags flags) { + return SaveEntry { + .clip = static_cast<bool>(flags & SaveFlags::Clip), + .matrix = static_cast<bool>(flags & SaveFlags::Matrix), + .layer = false + }; + } + + bool internalSave(SaveEntry saveEntry); + bool internalSave(SaveFlags::Flags flags) { + return internalSave(flagsToSaveEntry(flags)); + } + void internalSaveLayer(const SkCanvas::SaveLayerRec& layerRec) { + internalSave({ + .clip = true, + .matrix = true, + .layer = true + }); + internalClipRect(*layerRec.fBounds, SkClipOp::kIntersect); + } + + bool internalRestore(); + + void internalClipRect(const SkRect& rect, SkClipOp op); + void internalClipPath(const SkPath& path, SkClipOp op); + + SkIRect mInitialBounds; + FatVector<SaveEntry, 6> mSaveStack; + FatVector<SkMatrix, 6> mTransformStack; + FatVector<SkConservativeClip, 6> mClipStack; + + size_t mCurrentTransformIndex; + size_t mCurrentClipIndex; + + const SkConservativeClip& clip() const { + return mClipStack[mCurrentClipIndex]; + } + + SkConservativeClip& clip() { + return mClipStack[mCurrentClipIndex]; + } + +public: + int saveCount() const { return mSaveStack.size(); } + + SkRect getClipBounds() const; + bool quickRejectRect(float left, float top, float right, float bottom) const; + bool quickRejectPath(const SkPath& path) const; + + const SkMatrix& transform() const { + return mTransformStack[mCurrentTransformIndex]; + } + + SkMatrix& transform() { + return mTransformStack[mCurrentTransformIndex]; + } + + // For compat with existing HWUI Canvas interface + void getMatrix(SkMatrix* outMatrix) const { + *outMatrix = transform(); + } + + void setMatrix(const SkMatrix& matrix) { + transform() = matrix; + } + + void concat(const SkMatrix& matrix) { + transform().preConcat(matrix); + } + + void rotate(float degrees) { + SkMatrix m; + m.setRotate(degrees); + concat(m); + } + + void scale(float sx, float sy) { + SkMatrix m; + m.setScale(sx, sy); + concat(m); + } + + void skew(float sx, float sy) { + SkMatrix m; + m.setSkew(sx, sy); + concat(m); + } + + void translate(float dx, float dy) { + transform().preTranslate(dx, dy); + } +}; + +// Front-end canvas that handles queries, up-front state, and produces CanvasOp<> output downstream +template <typename CanvasOpReceiver> +class CanvasFrontend final : public CanvasStateHelper { +public: + template<class... Args> + CanvasFrontend(int width, int height, Args&&... args) : CanvasStateHelper(width, height), + mReceiver(std::forward<Args>(args)...) { } + ~CanvasFrontend() = default; + + void save(SaveFlags::Flags flags = SaveFlags::MatrixClip) { + if (internalSave(flagsToSaveEntry(flags))) { + submit<CanvasOpType::Save>({}); + } + } + + void restore() { + if (internalRestore()) { + submit<CanvasOpType::Restore>({}); + } + } + + template <CanvasOpType T> + void draw(CanvasOp<T>&& op) { + // The front-end requires going through certain front-doors, which these aren't. + static_assert(T != CanvasOpType::Save, "Must use CanvasFrontend::save() call instead"); + static_assert(T != CanvasOpType::Restore, "Must use CanvasFrontend::restore() call instead"); + + if constexpr (T == CanvasOpType::SaveLayer) { + internalSaveLayer(op.saveLayerRec); + } + if constexpr (T == CanvasOpType::SaveBehind) { + // Don't use internalSaveLayer as this doesn't apply clipping, it's a "regular" save + // But we do want to flag it as a layer, such that restore is Definitely Required + internalSave(saveEntryForLayer()); + } + if constexpr (T == CanvasOpType::ClipRect) { + internalClipRect(op.rect, op.op); + } + if constexpr (T == CanvasOpType::ClipPath) { + internalClipPath(op.path, op.op); + } + + submit(std::move(op)); + } + + const CanvasOpReceiver& receiver() const { return mReceiver; } + +private: + CanvasOpReceiver mReceiver; + + template <CanvasOpType T> + void submit(CanvasOp<T>&& op) { + mReceiver.push_container(CanvasOpContainer(std::move(op), transform())); + } +}; + +} // namespace android::uirenderer diff --git a/libs/hwui/canvas/CanvasOpRecorder.cpp b/libs/hwui/canvas/CanvasOpRecorder.cpp new file mode 100644 index 000000000000..bb968ee84670 --- /dev/null +++ b/libs/hwui/canvas/CanvasOpRecorder.cpp @@ -0,0 +1,22 @@ +/* + * 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 "CanvasOpRecorder.h" + +#include "CanvasOpBuffer.h" +#include "CanvasOps.h" + +namespace android::uirenderer {} // namespace android::uirenderer diff --git a/libs/hwui/canvas/CanvasOpRecorder.h b/libs/hwui/canvas/CanvasOpRecorder.h new file mode 100644 index 000000000000..7d95bc4785ea --- /dev/null +++ b/libs/hwui/canvas/CanvasOpRecorder.h @@ -0,0 +1,38 @@ +/* + * 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. + */ + +#pragma once + +#include "hwui/Canvas.h" +#include "CanvasOpBuffer.h" + +#include <vector> + +namespace android::uirenderer { + +// Interop with existing HWUI Canvas +class CanvasOpRecorder final : /* todo: public Canvas */ { +public: + // Transform ops +private: + struct SaveEntry { + + }; + + std::vector<SaveEntry> mSaveStack; +}; + +} // namespace android::uirenderer diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h index f94bae2746d9..4d67166dd8d2 100644 --- a/libs/hwui/hwui/Canvas.h +++ b/libs/hwui/hwui/Canvas.h @@ -18,6 +18,7 @@ #include <cutils/compiler.h> #include <utils/Functor.h> +#include <SaveFlags.h> #include <androidfw/ResourceTypes.h> #include "Properties.h" @@ -57,22 +58,6 @@ class SkiaDisplayList; using DisplayList = skiapipeline::SkiaDisplayList; } -namespace SaveFlags { - -// These must match the corresponding Canvas API constants. -enum { - Matrix = 0x01, - Clip = 0x02, - HasAlphaLayer = 0x04, - ClipToLayer = 0x10, - - // Helper constant - MatrixClip = Matrix | Clip, -}; -typedef uint32_t Flags; - -} // namespace SaveFlags - namespace uirenderer { namespace VectorDrawable { class Tree; diff --git a/libs/hwui/tests/unit/CanvasFrontendTests.cpp b/libs/hwui/tests/unit/CanvasFrontendTests.cpp new file mode 100644 index 000000000000..05b11795d90d --- /dev/null +++ b/libs/hwui/tests/unit/CanvasFrontendTests.cpp @@ -0,0 +1,213 @@ +/* + * 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/CanvasFrontend.h> +#include <canvas/CanvasOpBuffer.h> +#include <canvas/CanvasOps.h> +#include <canvas/CanvasOpRasterizer.h> + +#include <tests/common/CallCountingCanvas.h> + +#include "SkPictureRecorder.h" +#include "SkColor.h" +#include "SkLatticeIter.h" +#include "pipeline/skia/AnimatedDrawables.h" +#include <SkNoDrawCanvas.h> + +using namespace android; +using namespace android::uirenderer; +using namespace android::uirenderer::test; + +class CanvasOpCountingReceiver { +public: + template <CanvasOpType T> + void push_container(CanvasOpContainer<T>&& op) { + mOpCounts[static_cast<size_t>(T)] += 1; + } + + int operator[](CanvasOpType op) const { + return mOpCounts[static_cast<size_t>(op)]; + } + +private: + std::array<int, static_cast<size_t>(CanvasOpType::COUNT)> mOpCounts; +}; + +TEST(CanvasFrontend, saveCount) { + SkNoDrawCanvas skiaCanvas(100, 100); + CanvasFrontend<CanvasOpCountingReceiver> opCanvas(100, 100); + const auto& receiver = opCanvas.receiver(); + + EXPECT_EQ(1, skiaCanvas.getSaveCount()); + EXPECT_EQ(1, opCanvas.saveCount()); + + skiaCanvas.save(); + opCanvas.save(SaveFlags::MatrixClip); + EXPECT_EQ(2, skiaCanvas.getSaveCount()); + EXPECT_EQ(2, opCanvas.saveCount()); + + skiaCanvas.restore(); + opCanvas.restore(); + EXPECT_EQ(1, skiaCanvas.getSaveCount()); + EXPECT_EQ(1, opCanvas.saveCount()); + + skiaCanvas.restore(); + opCanvas.restore(); + EXPECT_EQ(1, skiaCanvas.getSaveCount()); + EXPECT_EQ(1, opCanvas.saveCount()); + + EXPECT_EQ(1, receiver[CanvasOpType::Save]); + EXPECT_EQ(1, receiver[CanvasOpType::Restore]); +} + +TEST(CanvasFrontend, transform) { + SkNoDrawCanvas skiaCanvas(100, 100); + CanvasFrontend<CanvasOpCountingReceiver> opCanvas(100, 100); + + skiaCanvas.translate(10, 10); + opCanvas.translate(10, 10); + EXPECT_EQ(skiaCanvas.getTotalMatrix(), opCanvas.transform()); + + { + skiaCanvas.save(); + opCanvas.save(SaveFlags::Matrix); + skiaCanvas.scale(2.0f, 1.125f); + opCanvas.scale(2.0f, 1.125f); + + EXPECT_EQ(skiaCanvas.getTotalMatrix(), opCanvas.transform()); + skiaCanvas.restore(); + opCanvas.restore(); + } + + EXPECT_EQ(skiaCanvas.getTotalMatrix(), opCanvas.transform()); + + { + skiaCanvas.save(); + opCanvas.save(SaveFlags::Matrix); + skiaCanvas.rotate(90.f); + opCanvas.rotate(90.f); + + EXPECT_EQ(skiaCanvas.getTotalMatrix(), opCanvas.transform()); + + { + skiaCanvas.save(); + opCanvas.save(SaveFlags::Matrix); + skiaCanvas.skew(5.0f, 2.25f); + opCanvas.skew(5.0f, 2.25f); + + EXPECT_EQ(skiaCanvas.getTotalMatrix(), opCanvas.transform()); + skiaCanvas.restore(); + opCanvas.restore(); + } + + skiaCanvas.restore(); + opCanvas.restore(); + } + + EXPECT_EQ(skiaCanvas.getTotalMatrix(), opCanvas.transform()); +} + +TEST(CanvasFrontend, drawOpTransform) { + CanvasFrontend<CanvasOpBuffer> opCanvas(100, 100); + const auto& receiver = opCanvas.receiver(); + + auto makeDrawRect = [] { + return CanvasOp<CanvasOpType::DrawRect>{ + .rect = SkRect::MakeWH(50, 50), + .paint = SkPaint(SkColors::kBlack), + }; + }; + + opCanvas.draw(makeDrawRect()); + + opCanvas.translate(10, 10); + opCanvas.draw(makeDrawRect()); + + opCanvas.save(); + opCanvas.scale(2.0f, 4.0f); + opCanvas.draw(makeDrawRect()); + opCanvas.restore(); + + opCanvas.save(); + opCanvas.translate(20, 15); + opCanvas.draw(makeDrawRect()); + opCanvas.save(); + opCanvas.rotate(90.f); + opCanvas.draw(makeDrawRect()); + opCanvas.restore(); + opCanvas.restore(); + + // Validate the results + std::vector<SkMatrix> transforms; + transforms.reserve(5); + receiver.for_each([&](auto op) { + // Filter for the DrawRect calls; ignore the save & restores + // (TODO: Add a filtered for_each variant to OpBuffer?) + if (op->type() == CanvasOpType::DrawRect) { + transforms.push_back(op->transform()); + } + }); + + EXPECT_EQ(transforms.size(), 5); + + { + // First result should be identity + const auto& result = transforms[0]; + EXPECT_EQ(SkMatrix::kIdentity_Mask, result.getType()); + EXPECT_EQ(SkMatrix::I(), result); + } + + { + // Should be translate 10, 10 + const auto& result = transforms[1]; + EXPECT_EQ(SkMatrix::kTranslate_Mask, result.getType()); + SkMatrix m; + m.setTranslate(10, 10); + EXPECT_EQ(m, result); + } + + { + // Should be translate 10, 10 + scale 2, 4 + const auto& result = transforms[2]; + EXPECT_EQ(SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask, result.getType()); + SkMatrix m; + m.setTranslate(10, 10); + m.preScale(2.0f, 4.0f); + EXPECT_EQ(m, result); + } + + { + // Should be translate 10, 10 + translate 20, 15 + const auto& result = transforms[3]; + EXPECT_EQ(SkMatrix::kTranslate_Mask, result.getType()); + SkMatrix m; + m.setTranslate(30, 25); + EXPECT_EQ(m, result); + } + + { + // Should be translate 10, 10 + translate 20, 15 + rotate 90 + const auto& result = transforms[4]; + EXPECT_EQ(SkMatrix::kTranslate_Mask | SkMatrix::kAffine_Mask | SkMatrix::kScale_Mask, + result.getType()); + SkMatrix m; + m.setTranslate(30, 25); + m.preRotate(90.f); + EXPECT_EQ(m, result); + } +}
\ No newline at end of file diff --git a/libs/hwui/tests/unit/CanvasOpTests.cpp b/libs/hwui/tests/unit/CanvasOpTests.cpp index b15c3221dd60..f186e55ec2e3 100644 --- a/libs/hwui/tests/unit/CanvasOpTests.cpp +++ b/libs/hwui/tests/unit/CanvasOpTests.cpp @@ -16,6 +16,7 @@ #include <gtest/gtest.h> +#include <canvas/CanvasFrontend.h> #include <canvas/CanvasOpBuffer.h> #include <canvas/CanvasOps.h> #include <canvas/CanvasOpRasterizer.h> @@ -26,6 +27,7 @@ #include "SkColor.h" #include "SkLatticeIter.h" #include "pipeline/skia/AnimatedDrawables.h" +#include <SkNoDrawCanvas.h> using namespace android; using namespace android::uirenderer; @@ -78,6 +80,21 @@ struct MockOp<MockTypes::Lifecycle> { using MockBuffer = OpBuffer<MockTypes, MockOpContainer>; +class CanvasOpCountingReceiver { +public: + template <CanvasOpType T> + void push_container(CanvasOpContainer<T>&& op) { + mOpCounts[static_cast<size_t>(T)] += 1; + } + + int operator[](CanvasOpType op) const { + return mOpCounts[static_cast<size_t>(op)]; + } + +private: + std::array<int, static_cast<size_t>(CanvasOpType::COUNT)> mOpCounts; +}; + template<typename T> static int countItems(const T& t) { int count = 0; @@ -614,4 +631,35 @@ TEST(CanvasOp, immediateRendering) { rasterizer.draw(op); EXPECT_EQ(1, canvas->drawRectCount); EXPECT_EQ(1, canvas->sumTotalDrawCalls()); +} + +TEST(CanvasOp, frontendSaveCount) { + SkNoDrawCanvas skiaCanvas(100, 100); + CanvasFrontend<CanvasOpCountingReceiver> opCanvas(100, 100); + const auto& receiver = opCanvas.receiver(); + + EXPECT_EQ(1, skiaCanvas.getSaveCount()); + EXPECT_EQ(1, opCanvas.saveCount()); + + skiaCanvas.save(); + opCanvas.save(SaveFlags::MatrixClip); + EXPECT_EQ(2, skiaCanvas.getSaveCount()); + EXPECT_EQ(2, opCanvas.saveCount()); + + skiaCanvas.restore(); + opCanvas.restore(); + EXPECT_EQ(1, skiaCanvas.getSaveCount()); + EXPECT_EQ(1, opCanvas.saveCount()); + + skiaCanvas.restore(); + opCanvas.restore(); + EXPECT_EQ(1, skiaCanvas.getSaveCount()); + EXPECT_EQ(1, opCanvas.saveCount()); + + EXPECT_EQ(1, receiver[Op::Save]); + EXPECT_EQ(1, receiver[Op::Restore]); +} + +TEST(CanvasOp, frontendTransform) { + }
\ No newline at end of file |