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
diff --git a/libs/hwui/tests/unit/CanvasFrontendTests.cpp b/libs/hwui/tests/unit/CanvasFrontendTests.cpp
new file mode 100644
index 0000000..05b1179
--- /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