Add TextOnPath support to new reorderer/renderer

bug:22480459

Change-Id: I302048ec09901420c15003e21e44a551cc59c7ad
diff --git a/libs/hwui/BakedOpDispatcher.cpp b/libs/hwui/BakedOpDispatcher.cpp
index 9e39797..a892b1b 100644
--- a/libs/hwui/BakedOpDispatcher.cpp
+++ b/libs/hwui/BakedOpDispatcher.cpp
@@ -705,6 +705,36 @@
     renderTextOp(renderer, op, state, clip, TextRenderType::Flush);
 }
 
+void BakedOpDispatcher::onTextOnPathOp(BakedOpRenderer& renderer, const TextOnPathOp& op, const BakedOpState& state) {
+    // Note: can't trust clipSideFlags since we record with unmappedBounds == clip.
+    // TODO: respect clipSideFlags, once we record with bounds
+    const Rect* renderTargetClip = &state.computedState.clipRect;
+
+    FontRenderer& fontRenderer = renderer.caches().fontRenderer.getFontRenderer();
+    fontRenderer.setFont(op.paint, SkMatrix::I());
+    fontRenderer.setTextureFiltering(true);
+
+    Rect layerBounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
+
+    int alpha = PaintUtils::getAlphaDirect(op.paint) * state.alpha;
+    SkXfermode::Mode mode = PaintUtils::getXfermodeDirect(op.paint);
+    TextDrawFunctor functor(&renderer, &state, renderTargetClip,
+            0.0f, 0.0f, false, alpha, mode, op.paint);
+
+    bool mustDirtyRenderTarget = renderer.offscreenRenderTarget();
+    const Rect localSpaceClip = state.computedState.computeLocalSpaceClip();
+    if (fontRenderer.renderTextOnPath(op.paint, &localSpaceClip,
+            reinterpret_cast<const char*>(op.glyphs), op.glyphCount,
+            op.path, op.hOffset, op.vOffset,
+            mustDirtyRenderTarget ? &layerBounds : nullptr, &functor)) {
+        if (mustDirtyRenderTarget) {
+            // manually dirty render target, since TextDrawFunctor won't
+            state.computedState.transform.mapRect(layerBounds);
+            renderer.dirtyRenderTarget(layerBounds);
+        }
+    }
+}
+
 void BakedOpDispatcher::onLayerOp(BakedOpRenderer& renderer, const LayerOp& op, const BakedOpState& state) {
     OffscreenBuffer* buffer = *op.layerHandle;
 
diff --git a/libs/hwui/BakedOpState.h b/libs/hwui/BakedOpState.h
index 9c836a0..b12c0c9 100644
--- a/libs/hwui/BakedOpState.h
+++ b/libs/hwui/BakedOpState.h
@@ -124,6 +124,15 @@
         clipSideFlags = OpClipSideFlags::Full;
     }
 
+    Rect computeLocalSpaceClip() const {
+        Matrix4 inverse;
+        inverse.loadInverse(transform);
+
+        Rect outClip(clipRect);
+        inverse.mapRect(outClip);
+        return outClip;
+    }
+
     Matrix4 transform;
     Rect clipRect;
     int clipSideFlags = 0;
diff --git a/libs/hwui/OpReorderer.cpp b/libs/hwui/OpReorderer.cpp
index 6d90a42..e147211 100644
--- a/libs/hwui/OpReorderer.cpp
+++ b/libs/hwui/OpReorderer.cpp
@@ -90,7 +90,9 @@
     }
 
     MergingOpBatch(batchid_t batchId, BakedOpState* op)
-            : BatchBase(batchId, op, true) {
+            : BatchBase(batchId, op, true)
+            , mClipSideFlags(op->computedState.clipSideFlags)
+            , mClipRect(op->computedState.clipRect) {
     }
 
     /*
@@ -202,11 +204,11 @@
         if (newClipSideFlags & OpClipSideFlags::Bottom) mClipRect.bottom = opClip.bottom;
     }
 
-    bool getClipSideFlags() const { return mClipSideFlags; }
+    int getClipSideFlags() const { return mClipSideFlags; }
     const Rect& getClipRect() const { return mClipRect; }
 
 private:
-    int mClipSideFlags = 0;
+    int mClipSideFlags;
     Rect mClipRect;
 };
 
@@ -308,12 +310,6 @@
                     mergingBatch->getClipSideFlags(),
                     mergingBatch->getClipRect()
             };
-            if (data.clipSideFlags) {
-                // if right or bottom sides aren't used to clip, init them to viewport bounds
-                // in the clip rect, so it can be used to scissor
-                if (!(data.clipSideFlags & OpClipSideFlags::Right)) data.clip.right = width;
-                if (!(data.clipSideFlags & OpClipSideFlags::Bottom)) data.clip.bottom = height;
-            }
             mergedReceivers[opId](arg, data);
         } else {
             for (const BakedOpState* op : batch->getOps()) {
@@ -850,14 +846,16 @@
     currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Vertices);
 }
 
+static batchid_t textBatchId(const SkPaint& paint) {
+    // TODO: better handling of shader (since we won't care about color then)
+    return paint.getColor() == SK_ColorBLACK ? OpBatchType::Text : OpBatchType::ColorText;
+}
+
 void OpReorderer::deferTextOp(const TextOp& op) {
     BakedOpState* bakedState = tryBakeOpState(op);
     if (!bakedState) return; // quick rejected
 
-    // TODO: better handling of shader (since we won't care about color then)
-    batchid_t batchId = op.paint->getColor() == SK_ColorBLACK
-            ? OpBatchType::Text : OpBatchType::ColorText;
-
+    batchid_t batchId = textBatchId(*(op.paint));
     if (bakedState->computedState.transform.isPureTranslate()
             && PaintUtils::getXfermodeDirect(op.paint) == SkXfermode::kSrcOver_Mode) {
         mergeid_t mergeId = reinterpret_cast<mergeid_t>(op.paint->getColor());
@@ -867,6 +865,12 @@
     }
 }
 
+void OpReorderer::deferTextOnPathOp(const TextOnPathOp& op) {
+    BakedOpState* bakedState = tryBakeOpState(op);
+    if (!bakedState) return; // quick rejected
+    currentLayer().deferUnmergeableOp(mAllocator, bakedState, textBatchId(*(op.paint)));
+}
+
 void OpReorderer::saveForLayer(uint32_t layerWidth, uint32_t layerHeight,
         float contentTranslateX, float contentTranslateY,
         const Rect& repaintRect,
diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h
index b58b774..bb23005 100644
--- a/libs/hwui/RecordedOp.h
+++ b/libs/hwui/RecordedOp.h
@@ -62,6 +62,7 @@
         U_OP_FN(ShadowOp) \
         U_OP_FN(SimpleRectsOp) \
         M_OP_FN(TextOp) \
+        U_OP_FN(TextOnPathOp) \
         U_OP_FN(BeginLayerOp) \
         U_OP_FN(EndLayerOp) \
         U_OP_FN(LayerOp)
@@ -327,6 +328,23 @@
     const float y;
 };
 
+struct TextOnPathOp : RecordedOp {
+    TextOnPathOp(BASE_PARAMS, const glyph_t* glyphs, int glyphCount,
+            const SkPath* path, float hOffset, float vOffset)
+            : SUPER(TextOnPathOp)
+            , glyphs(glyphs)
+            , glyphCount(glyphCount)
+            , path(path)
+            , hOffset(hOffset)
+            , vOffset(vOffset) {}
+    const glyph_t* glyphs;
+    const int glyphCount;
+
+    const SkPath* path;
+    const float hOffset;
+    const float vOffset;
+};
+
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 // Layers
 ////////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 7f035e5..46ae790 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -503,9 +503,15 @@
     drawTextDecorations(x, y, totalAdvance, paint);
 }
 
-void RecordingCanvas::drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path,
+void RecordingCanvas::drawTextOnPath(const uint16_t* glyphs, int glyphCount, const SkPath& path,
             float hOffset, float vOffset, const SkPaint& paint) {
-    LOG_ALWAYS_FATAL("TODO!");
+    if (!glyphs || glyphCount <= 0 || PaintUtils::paintWillNotDrawText(paint)) return;
+    glyphs = refBuffer<glyph_t>(glyphs, glyphCount);
+    addOp(new (alloc()) TextOnPathOp(
+            mState.getRenderTargetClipBounds(), // TODO: explicitly define bounds
+            *(mState.currentSnapshot()->transform),
+            mState.getRenderTargetClipBounds(),
+            refPaint(&paint), glyphs, glyphCount, refPath(&path), hOffset, vOffset));
 }
 
 void RecordingCanvas::drawBitmap(const SkBitmap* bitmap, const SkPaint* paint) {
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index 49bdba8..ce7d135 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -189,10 +189,10 @@
             const SkPaint* paint) override;
 
     // Text
-    virtual void drawText(const uint16_t* glyphs, const float* positions, int count,
+    virtual void drawText(const uint16_t* glyphs, const float* positions, int glyphCount,
             const SkPaint& paint, float x, float y, float boundsLeft, float boundsTop,
             float boundsRight, float boundsBottom, float totalAdvance) override;
-    virtual void drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path,
+    virtual void drawTextOnPath(const uint16_t* glyphs, int glyphCount, const SkPath& path,
             float hOffset, float vOffset, const SkPaint& paint) override;
     virtual bool drawTextAbsolutePos() const override { return false; }
 
diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp
index 6cef852..4c8d23d 100644
--- a/libs/hwui/tests/common/TestUtils.cpp
+++ b/libs/hwui/tests/common/TestUtils.cpp
@@ -41,11 +41,8 @@
     // drawing text requires GlyphID TextEncoding (which JNI layer would have done)
     LOG_ALWAYS_FATAL_IF(paint.getTextEncoding() != SkPaint::kGlyphID_TextEncoding,
             "must use glyph encoding");
-
-    SkMatrix identity;
-    identity.setIdentity();
     SkSurfaceProps surfaceProps(0, kUnknown_SkPixelGeometry);
-    SkAutoGlyphCacheNoGamma autoCache(paint, &surfaceProps, &identity);
+    SkAutoGlyphCacheNoGamma autoCache(paint, &surfaceProps, &SkMatrix::I());
 
     float totalAdvance = 0;
     std::vector<glyph_t> glyphs;
@@ -89,5 +86,21 @@
                 bounds.left, bounds.top, bounds.right, bounds.bottom, totalAdvance);
 }
 
+void TestUtils::drawTextToCanvas(TestCanvas* canvas, const char* text,
+        const SkPaint& paint, const SkPath& path) {
+    // drawing text requires GlyphID TextEncoding (which JNI layer would have done)
+    LOG_ALWAYS_FATAL_IF(paint.getTextEncoding() != SkPaint::kGlyphID_TextEncoding,
+            "must use glyph encoding");
+    SkSurfaceProps surfaceProps(0, kUnknown_SkPixelGeometry);
+    SkAutoGlyphCacheNoGamma autoCache(paint, &surfaceProps, &SkMatrix::I());
+
+    std::vector<glyph_t> glyphs;
+    while (*text != '\0') {
+        SkUnichar unichar = SkUTF8_NextUnichar(&text);
+        glyphs.push_back(autoCache.getCache()->unicharToGlyph(unichar));
+    }
+    canvas->drawTextOnPath(glyphs.data(), glyphs.size(), path, 0, 0, paint);
+}
+
 } /* namespace uirenderer */
 } /* namespace android */
diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h
index 0af9939..4f84474 100644
--- a/libs/hwui/tests/common/TestUtils.h
+++ b/libs/hwui/tests/common/TestUtils.h
@@ -191,6 +191,9 @@
     static void drawTextToCanvas(TestCanvas* canvas, const char* text,
             const SkPaint& paint, float x, float y);
 
+    static void drawTextToCanvas(TestCanvas* canvas, const char* text,
+            const SkPaint& paint, const SkPath& path);
+
 private:
     static void syncHierarchyPropertiesAndDisplayListImpl(RenderNode* node) {
         node->syncProperties();
diff --git a/libs/hwui/tests/common/scenes/OvalAnimation.cpp b/libs/hwui/tests/common/scenes/OvalAnimation.cpp
index 082c628..e56f2f9 100644
--- a/libs/hwui/tests/common/scenes/OvalAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/OvalAnimation.cpp
@@ -15,6 +15,7 @@
  */
 
 #include "TestSceneBase.h"
+#include "utils/Color.h"
 
 class OvalAnimation;
 
@@ -28,12 +29,12 @@
 public:
     sp<RenderNode> card;
     void createContent(int width, int height, TestCanvas& canvas) override {
-        canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+        canvas.drawColor(Color::White, SkXfermode::kSrcOver_Mode);
         card = TestUtils::createNode(0, 0, 200, 200,
                 [](RenderProperties& props, TestCanvas& canvas) {
             SkPaint paint;
             paint.setAntiAlias(true);
-            paint.setColor(0xFF000000);
+            paint.setColor(Color::Black);
             canvas.drawOval(0, 0, 200, 200, paint);
         });
         canvas.drawRenderNode(card.get());
diff --git a/libs/hwui/tests/common/scenes/TextAnimation.cpp b/libs/hwui/tests/common/scenes/TextAnimation.cpp
new file mode 100644
index 0000000..1823db2
--- /dev/null
+++ b/libs/hwui/tests/common/scenes/TextAnimation.cpp
@@ -0,0 +1,60 @@
+/*
+ * 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 "TestSceneBase.h"
+#include "utils/Color.h"
+
+class TextAnimation;
+
+static TestScene::Registrar _Text(TestScene::Info{
+    "text",
+    "Draws a bunch of text.",
+    TestScene::simpleCreateScene<TextAnimation>
+});
+
+class TextAnimation : public TestScene {
+public:
+    sp<RenderNode> card;
+    void createContent(int width, int height, TestCanvas& canvas) override {
+        canvas.drawColor(Color::White, SkXfermode::kSrcOver_Mode);
+        card = TestUtils::createNode(0, 0, width, height,
+                [](RenderProperties& props, TestCanvas& canvas) {
+            SkPaint paint;
+            paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+            paint.setAntiAlias(true);
+            paint.setTextSize(50);
+
+            paint.setColor(Color::Black);
+            for (int i = 0; i < 10; i++) {
+                TestUtils::drawTextToCanvas(&canvas, "Test string", paint, 400, i * 100);
+            }
+
+            SkPath path;
+            path.addOval(SkRect::MakeLTRB(100, 100, 300, 300));
+
+            paint.setColor(Color::Blue_500);
+            TestUtils::drawTextToCanvas(&canvas, "This is a neat circle of text!", paint, path);
+        });
+        canvas.drawRenderNode(card.get());
+    }
+
+    void doFrame(int frameNr) override {
+        int curFrame = frameNr % 150;
+        card->mutateStagingProperties().setTranslationX(curFrame);
+        card->mutateStagingProperties().setTranslationY(curFrame);
+        card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+    }
+};
diff --git a/libs/hwui/tests/unit/BakedOpStateTests.cpp b/libs/hwui/tests/unit/BakedOpStateTests.cpp
index 33eff5b..f9f5316 100644
--- a/libs/hwui/tests/unit/BakedOpStateTests.cpp
+++ b/libs/hwui/tests/unit/BakedOpStateTests.cpp
@@ -56,6 +56,28 @@
     }
 }
 
+TEST(ResolvedRenderState, computeLocalSpaceClip) {
+    Matrix4 translate10x20;
+    translate10x20.loadTranslate(10, 20, 0);
+
+    SkPaint paint;
+    RectOp recordedOp(Rect(1000, 1000), translate10x20, Rect(100, 200), &paint);
+    {
+        // recorded with transform, no parent transform
+        auto parentSnapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(100, 200));
+        ResolvedRenderState state(*parentSnapshot, recordedOp, false);
+        EXPECT_EQ(Rect(-10, -20, 90, 180), state.computeLocalSpaceClip())
+            << "Local clip rect should be 100x200, offset by -10,-20";
+    }
+    {
+        // recorded with transform + parent transform
+        auto parentSnapshot = TestUtils::makeSnapshot(translate10x20, Rect(100, 200));
+        ResolvedRenderState state(*parentSnapshot, recordedOp, false);
+        EXPECT_EQ(Rect(-10, -20, 80, 160), state.computeLocalSpaceClip())
+            << "Local clip rect should be 90x190, offset by -10,-20";
+    }
+}
+
 const float HAIRLINE = 0.0f;
 
 // Note: bounds will be conservative, but not precise for non-hairline
diff --git a/libs/hwui/tests/unit/OpReordererTests.cpp b/libs/hwui/tests/unit/OpReordererTests.cpp
index ac356a47..ab0cc87 100644
--- a/libs/hwui/tests/unit/OpReordererTests.cpp
+++ b/libs/hwui/tests/unit/OpReordererTests.cpp
@@ -221,7 +221,35 @@
             << "Expect number of ops = 2 * loop count";
 }
 
-TEST(OpReorderer, textStrikethroughBatching) {
+TEST(OpReorderer, textMerging) {
+    class TextMergingTestRenderer : public TestRendererBase {
+    public:
+        void onMergedTextOps(const MergedBakedOpList& opList) override {
+            EXPECT_EQ(0, mIndex);
+            mIndex += opList.count;
+            EXPECT_EQ(2u, opList.count);
+            EXPECT_EQ(OpClipSideFlags::Top, opList.clipSideFlags);
+            EXPECT_EQ(OpClipSideFlags::Top, opList.states[0]->computedState.clipSideFlags);
+            EXPECT_EQ(OpClipSideFlags::None, opList.states[1]->computedState.clipSideFlags);
+        }
+    };
+    auto node = TestUtils::createNode(0, 0, 400, 400,
+            [](RenderProperties& props, TestCanvas& canvas) {
+        SkPaint paint;
+        paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+        paint.setAntiAlias(true);
+        paint.setTextSize(50);
+        TestUtils::drawTextToCanvas(&canvas, "Test string1", paint, 100, 0); // will be top clipped
+        TestUtils::drawTextToCanvas(&canvas, "Test string1", paint, 100, 100); // not clipped
+    });
+    OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(400, 400), 400, 400,
+            createSyncedNodeList(node), sLightCenter);
+    TextMergingTestRenderer renderer;
+    reorderer.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(2, renderer.getIndex()) << "Expect 2 ops";
+}
+
+TEST(OpReorderer, textStrikethrough) {
     const int LOOPS = 5;
     class TextStrikethroughTestRenderer : public TestRendererBase {
     public:
@@ -250,7 +278,7 @@
     TextStrikethroughTestRenderer renderer;
     reorderer.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(2 * LOOPS, renderer.getIndex())
-            << "Expect number of ops = 2 * loop count"; // TODO: force no merging
+            << "Expect number of ops = 2 * loop count";
 }
 
 TEST(OpReorderer, renderNode) {