Initial commit of new Canvas operation recording / replay

Done:
- drawRect, drawBitmap, drawColor, drawPaint, drawRenderNode, drawRegion
- Recording with new DisplayList format
- batching & reordering
- Stateless op reorder
- Stateless op rendering
- Frame lifecycle (clear, geterror, cleanup)

Not done:
- SaveLayer (clipped and unclipped)
- HW layers
- Complex clipping
- Ripple projection
- Z reordering
- Z shadows
- onDefer prefetching (text + task kickoff)
- round rect clip
- linear allocation for std collections
- AssetAtlas support

Change-Id: Iaf98c1a3aeab5fa47cc8f9c6d964420abc0e7691
diff --git a/libs/hwui/unit_tests/BakedOpStateTests.cpp b/libs/hwui/unit_tests/BakedOpStateTests.cpp
new file mode 100644
index 0000000..82aebea
--- /dev/null
+++ b/libs/hwui/unit_tests/BakedOpStateTests.cpp
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2015 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 <BakedOpState.h>
+#include <RecordedOp.h>
+#include <unit_tests/TestUtils.h>
+
+namespace android {
+namespace uirenderer {
+
+TEST(ResolvedRenderState, resolution) {
+    Matrix4 identity;
+    identity.loadIdentity();
+
+    Matrix4 translate10x20;
+    translate10x20.loadTranslate(10, 20, 0);
+
+    SkPaint paint;
+    RectOp recordedOp(Rect(30, 40, 100, 200), translate10x20, Rect(0, 0, 100, 200), &paint);
+    {
+        // recorded with transform, no parent transform
+        auto parentSnapshot = TestUtils::makeSnapshot(identity, Rect(0, 0, 100, 200));
+        ResolvedRenderState state(*parentSnapshot, recordedOp);
+        EXPECT_MATRIX_APPROX_EQ(state.transform, translate10x20);
+        EXPECT_EQ(state.clipRect, Rect(0, 0, 100, 200));
+        EXPECT_EQ(state.clippedBounds, Rect(40, 60, 100, 200)); // translated and also clipped
+    }
+    {
+        // recorded with transform and parent transform
+        auto parentSnapshot = TestUtils::makeSnapshot(translate10x20, Rect(0, 0, 100, 200));
+        ResolvedRenderState state(*parentSnapshot, recordedOp);
+
+        Matrix4 expectedTranslate;
+        expectedTranslate.loadTranslate(20, 40, 0);
+        EXPECT_MATRIX_APPROX_EQ(state.transform, expectedTranslate);
+
+        // intersection of parent & transformed child clip
+        EXPECT_EQ(state.clipRect, Rect(10, 20, 100, 200));
+
+        // translated and also clipped
+        EXPECT_EQ(state.clippedBounds, Rect(50, 80, 100, 200));
+    }
+}
+
+TEST(BakedOpState, constructAndReject) {
+    LinearAllocator allocator;
+
+    Matrix4 identity;
+    identity.loadIdentity();
+
+    Matrix4 translate100x0;
+    translate100x0.loadTranslate(100, 0, 0);
+
+    SkPaint paint;
+    {
+        RectOp rejectOp(Rect(30, 40, 100, 200), translate100x0, Rect(0, 0, 100, 200), &paint);
+        auto snapshot = TestUtils::makeSnapshot(identity, Rect(0, 0, 100, 200));
+        BakedOpState* bakedOp = BakedOpState::tryConstruct(allocator, *snapshot, rejectOp);
+
+        EXPECT_EQ(bakedOp, nullptr); // rejected by clip, so not constructed
+        EXPECT_LE(allocator.usedSize(), 8u); // no significant allocation space used for rejected op
+    }
+    {
+        RectOp successOp(Rect(30, 40, 100, 200), identity, Rect(0, 0, 100, 200), &paint);
+        auto snapshot = TestUtils::makeSnapshot(identity, Rect(0, 0, 100, 200));
+        BakedOpState* bakedOp = BakedOpState::tryConstruct(allocator, *snapshot, successOp);
+
+        EXPECT_NE(bakedOp, nullptr); // NOT rejected by clip, so will be constructed
+        EXPECT_GT(allocator.usedSize(), 64u); // relatively large alloc for non-rejected op
+    }
+}
+
+#define UNSUPPORTED_OP(Info, Type) \
+        static void on##Type(Info*, const Type&, const BakedOpState&) { FAIL(); }
+
+class Info {
+public:
+    int index = 0;
+};
+
+}
+}
diff --git a/libs/hwui/unit_tests/ClipAreaTests.cpp b/libs/hwui/unit_tests/ClipAreaTests.cpp
index 0c5e5e7..d6192df 100644
--- a/libs/hwui/unit_tests/ClipAreaTests.cpp
+++ b/libs/hwui/unit_tests/ClipAreaTests.cpp
@@ -101,10 +101,9 @@
     EXPECT_FALSE(area.isEmpty());
     EXPECT_FALSE(area.isSimple());
     EXPECT_FALSE(area.isRectangleList());
+
     Rect clipRect(area.getClipRect());
-    clipRect.dump("clipRect");
     Rect expected(0, 0, r * 2, r * 2);
-    expected.dump("expected");
     EXPECT_EQ(expected, clipRect);
     SkRegion clipRegion(area.getClipRegion());
     auto skRect(clipRegion.getBounds());
diff --git a/libs/hwui/unit_tests/OpReordererTests.cpp b/libs/hwui/unit_tests/OpReordererTests.cpp
new file mode 100644
index 0000000..fcaea1e
--- /dev/null
+++ b/libs/hwui/unit_tests/OpReordererTests.cpp
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2015 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 <BakedOpState.h>
+#include <OpReorderer.h>
+#include <RecordedOp.h>
+#include <RecordingCanvas.h>
+#include <unit_tests/TestUtils.h>
+
+#include <unordered_map>
+
+namespace android {
+namespace uirenderer {
+
+#define UNSUPPORTED_OP(Info, Type) \
+        static void on##Type(Info*, const Type&, const BakedOpState&) { FAIL(); }
+
+class Info {
+public:
+    int index = 0;
+};
+
+class SimpleReceiver {
+public:
+    static void onBitmapOp(Info* info, const BitmapOp& op, const BakedOpState& state) {
+        EXPECT_EQ(1, info->index++);
+    }
+    static void onRectOp(Info* info, const RectOp& op, const BakedOpState& state) {
+        EXPECT_EQ(0, info->index++);
+    }
+    UNSUPPORTED_OP(Info, RenderNodeOp)
+    UNSUPPORTED_OP(Info, SimpleRectsOp)
+    static void startFrame(Info& info) {}
+    static void endFrame(Info& info) {}
+};
+TEST(OpReorderer, simple) {
+    auto dld = TestUtils::createDLD<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
+        SkBitmap bitmap;
+        bitmap.setInfo(SkImageInfo::MakeUnknown(25, 25));
+
+        canvas.drawRect(0, 0, 100, 200, SkPaint());
+        canvas.drawBitmap(bitmap, 10, 10, nullptr);
+    });
+
+    OpReorderer reorderer;
+    reorderer.defer(200, 200, dld->getChunks(), dld->getOps());
+
+    Info info;
+    reorderer.replayBakedOps<SimpleReceiver>(&info);
+}
+
+
+static int SIMPLE_BATCHING_LOOPS = 5;
+class SimpleBatchingReceiver {
+public:
+    static void onBitmapOp(Info* info, const BitmapOp& op, const BakedOpState& state) {
+        EXPECT_TRUE(info->index++ >= SIMPLE_BATCHING_LOOPS);
+    }
+    static void onRectOp(Info* info, const RectOp& op, const BakedOpState& state) {
+        EXPECT_TRUE(info->index++ < SIMPLE_BATCHING_LOOPS);
+    }
+    UNSUPPORTED_OP(Info, RenderNodeOp)
+    UNSUPPORTED_OP(Info, SimpleRectsOp)
+    static void startFrame(Info& info) {}
+    static void endFrame(Info& info) {}
+};
+TEST(OpReorderer, simpleBatching) {
+    auto dld = TestUtils::createDLD<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+        SkBitmap bitmap;
+        bitmap.setInfo(SkImageInfo::MakeUnknown(10, 10));
+
+        // Alternate between drawing rects and bitmaps, with bitmaps overlapping rects.
+        // Rects don't overlap bitmaps, so bitmaps should be brought to front as a group.
+        canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+        for (int i = 0; i < SIMPLE_BATCHING_LOOPS; i++) {
+            canvas.translate(0, 10);
+            canvas.drawRect(0, 0, 10, 10, SkPaint());
+            canvas.drawBitmap(bitmap, 5, 0, nullptr);
+        }
+        canvas.restore();
+    });
+
+    OpReorderer reorderer;
+    reorderer.defer(200, 200, dld->getChunks(), dld->getOps());
+
+    Info info;
+    reorderer.replayBakedOps<SimpleBatchingReceiver>(&info);
+    EXPECT_EQ(2 * SIMPLE_BATCHING_LOOPS, info.index); // 2 x loops ops, because no merging (TODO: force no merging)
+}
+
+class RenderNodeReceiver {
+public:
+    UNSUPPORTED_OP(Info, BitmapOp)
+    static void onRectOp(Info* info, const RectOp& op, const BakedOpState& state) {
+        switch(info->index++) {
+        case 0:
+            EXPECT_EQ(Rect(0, 0, 200, 200), state.computedState.clippedBounds);
+            EXPECT_EQ(SK_ColorDKGRAY, op.paint->getColor());
+            break;
+        case 1:
+            EXPECT_EQ(Rect(50, 50, 150, 150), state.computedState.clippedBounds);
+            EXPECT_EQ(SK_ColorWHITE, op.paint->getColor());
+            break;
+        default:
+            FAIL();
+        }
+    }
+    UNSUPPORTED_OP(Info, RenderNodeOp)
+    UNSUPPORTED_OP(Info, SimpleRectsOp)
+    static void startFrame(Info& info) {}
+    static void endFrame(Info& info) {}
+};
+TEST(OpReorderer, renderNode) {
+    sp<RenderNode> child = TestUtils::createNode<RecordingCanvas>(10, 10, 110, 110, [](RecordingCanvas& canvas) {
+        SkPaint paint;
+        paint.setColor(SK_ColorWHITE);
+        canvas.drawRect(0, 0, 100, 100, paint);
+    });
+
+    RenderNode* childPtr = child.get();
+    sp<RenderNode> parent = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200, [childPtr](RecordingCanvas& canvas) {
+            SkPaint paint;
+            paint.setColor(SK_ColorDKGRAY);
+            canvas.drawRect(0, 0, 200, 200, paint);
+
+            canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+            canvas.translate(40, 40);
+            canvas.drawRenderNode(childPtr);
+            canvas.restore();
+    });
+
+    TestUtils::syncNodePropertiesAndDisplayList(child);
+    TestUtils::syncNodePropertiesAndDisplayList(parent);
+
+    std::vector< sp<RenderNode> > nodes;
+    nodes.push_back(parent.get());
+
+    OpReorderer reorderer;
+    reorderer.defer(200, 200, nodes);
+
+    Info info;
+    reorderer.replayBakedOps<RenderNodeReceiver>(&info);
+}
+
+}
+}
diff --git a/libs/hwui/unit_tests/RecordingCanvasTests.cpp b/libs/hwui/unit_tests/RecordingCanvasTests.cpp
new file mode 100644
index 0000000..c813833
--- /dev/null
+++ b/libs/hwui/unit_tests/RecordingCanvasTests.cpp
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2015 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 <RecordedOp.h>
+#include <RecordingCanvas.h>
+#include <unit_tests/TestUtils.h>
+
+namespace android {
+namespace uirenderer {
+
+static void playbackOps(const std::vector<DisplayListData::Chunk>& chunks,
+        const std::vector<RecordedOp*>& ops, std::function<void(const RecordedOp&)> opReciever) {
+    for (const DisplayListData::Chunk& chunk : chunks) {
+        for (size_t opIndex = chunk.beginOpIndex; opIndex < chunk.endOpIndex; opIndex++) {
+            opReciever(*ops[opIndex]);
+        }
+    }
+}
+
+TEST(RecordingCanvas, emptyPlayback) {
+    auto dld = TestUtils::createDLD<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
+        canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+        canvas.restore();
+    });
+    playbackOps(dld->getChunks(), dld->getOps(), [](const RecordedOp& op) { FAIL(); });
+}
+
+TEST(RecordingCanvas, testSimpleRectRecord) {
+    auto dld = TestUtils::createDLD<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
+        canvas.drawRect(10, 20, 90, 180, SkPaint());
+    });
+
+    int count = 0;
+    playbackOps(dld->getChunks(), dld->getOps(), [&count](const RecordedOp& op) {
+        count++;
+        ASSERT_EQ(RecordedOpId::RectOp, op.opId);
+        ASSERT_EQ(Rect(0, 0, 100, 200), op.localClipRect);
+        ASSERT_EQ(Rect(10, 20, 90, 180), op.unmappedBounds);
+    });
+    ASSERT_EQ(1, count); // only one observed
+}
+
+TEST(RecordingCanvas, backgroundAndImage) {
+    auto dld = TestUtils::createDLD<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
+        SkBitmap bitmap;
+        bitmap.setInfo(SkImageInfo::MakeUnknown(25, 25));
+        SkPaint paint;
+        paint.setColor(SK_ColorBLUE);
+
+        canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+        {
+            // a background!
+            canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+            canvas.drawRect(0, 0, 100, 200, paint);
+            canvas.restore();
+        }
+        {
+            // an image!
+            canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+            canvas.translate(25, 25);
+            canvas.scale(2, 2);
+            canvas.drawBitmap(bitmap, 0, 0, nullptr);
+            canvas.restore();
+        }
+        canvas.restore();
+    });
+
+    int count = 0;
+    playbackOps(dld->getChunks(), dld->getOps(), [&count](const RecordedOp& op) {
+        if (count == 0) {
+            ASSERT_EQ(RecordedOpId::RectOp, op.opId);
+            ASSERT_NE(nullptr, op.paint);
+            EXPECT_EQ(SK_ColorBLUE, op.paint->getColor());
+            EXPECT_EQ(Rect(0, 0, 100, 200), op.unmappedBounds);
+            EXPECT_EQ(Rect(0, 0, 100, 200), op.localClipRect);
+
+            Matrix4 expectedMatrix;
+            expectedMatrix.loadIdentity();
+            EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
+        } else {
+            ASSERT_EQ(RecordedOpId::BitmapOp, op.opId);
+            EXPECT_EQ(nullptr, op.paint);
+            EXPECT_EQ(Rect(0, 0, 25, 25), op.unmappedBounds);
+            EXPECT_EQ(Rect(0, 0, 100, 200), op.localClipRect);
+
+            Matrix4 expectedMatrix;
+            expectedMatrix.loadTranslate(25, 25, 0);
+            expectedMatrix.scale(2, 2, 1);
+            EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
+        }
+        count++;
+    });
+    ASSERT_EQ(2, count); // two draws observed
+}
+
+}
+}
diff --git a/libs/hwui/unit_tests/TestUtils.h b/libs/hwui/unit_tests/TestUtils.h
new file mode 100644
index 0000000..257dd28
--- /dev/null
+++ b/libs/hwui/unit_tests/TestUtils.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+#ifndef TEST_UTILS_H
+#define TEST_UTILS_H
+
+#include <Matrix.h>
+#include <Snapshot.h>
+#include <RenderNode.h>
+
+#include <memory>
+
+namespace android {
+namespace uirenderer {
+
+#define EXPECT_MATRIX_APPROX_EQ(a, b) \
+    EXPECT_TRUE(TestUtils::matricesAreApproxEqual(a, b))
+
+class TestUtils {
+public:
+    static bool matricesAreApproxEqual(const Matrix4& a, const Matrix4& b) {
+        for (int i = 0; i < 16; i++) {
+            if (!MathUtils::areEqual(a[i], b[i])) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    static std::unique_ptr<Snapshot> makeSnapshot(const Matrix4& transform, const Rect& clip) {
+        std::unique_ptr<Snapshot> snapshot(new Snapshot());
+        snapshot->clip(clip.left, clip.top, clip.right, clip.bottom, SkRegion::kReplace_Op);
+        *(snapshot->transform) = transform;
+        return snapshot;
+    }
+
+    template<class CanvasType>
+    static std::unique_ptr<DisplayListData> createDLD(int width, int height,
+            std::function<void(CanvasType& canvas)> canvasCallback) {
+        CanvasType canvas(width, height);
+        canvasCallback(canvas);
+        return std::unique_ptr<DisplayListData>(canvas.finishRecording());
+    }
+
+    template<class CanvasType>
+    static sp<RenderNode> createNode(int left, int top, int right, int bottom,
+            std::function<void(CanvasType& canvas)> canvasCallback) {
+        sp<RenderNode> node = new RenderNode();
+        node->mutateStagingProperties().setLeftTopRightBottom(left, top, right, bottom);
+        node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+
+        CanvasType canvas(
+                node->stagingProperties().getWidth(), node->stagingProperties().getHeight());
+        canvasCallback(canvas);
+        node->setStagingDisplayList(canvas.finishRecording());
+        return node;
+    }
+
+    static void syncNodePropertiesAndDisplayList(sp<RenderNode>& node) {
+        node->syncProperties();
+        node->syncDisplayList();
+    }
+}; // class TestUtils
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif /* TEST_UTILS_H */