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) {