diff options
author | 2015-12-15 10:34:36 -0800 | |
---|---|---|
committer | 2015-12-16 10:27:17 -0800 | |
commit | d7448e65e243754f31890baef29dff187dc2e5e5 (patch) | |
tree | 49c1784cf2cccce9618ee0412ee311d0fdecce83 | |
parent | 20754c5a112e418c11cc88176283db2c4bf2efd6 (diff) |
Add TextOnPath support to new reorderer/renderer
bug:22480459
Change-Id: I302048ec09901420c15003e21e44a551cc59c7ad
-rw-r--r-- | libs/hwui/BakedOpDispatcher.cpp | 30 | ||||
-rw-r--r-- | libs/hwui/BakedOpState.h | 9 | ||||
-rw-r--r-- | libs/hwui/OpReorderer.cpp | 30 | ||||
-rw-r--r-- | libs/hwui/RecordedOp.h | 18 | ||||
-rw-r--r-- | libs/hwui/RecordingCanvas.cpp | 10 | ||||
-rw-r--r-- | libs/hwui/RecordingCanvas.h | 4 | ||||
-rw-r--r-- | libs/hwui/tests/common/TestUtils.cpp | 21 | ||||
-rw-r--r-- | libs/hwui/tests/common/TestUtils.h | 3 | ||||
-rw-r--r-- | libs/hwui/tests/common/scenes/OvalAnimation.cpp | 5 | ||||
-rw-r--r-- | libs/hwui/tests/common/scenes/TextAnimation.cpp | 60 | ||||
-rw-r--r-- | libs/hwui/tests/unit/BakedOpStateTests.cpp | 22 | ||||
-rw-r--r-- | libs/hwui/tests/unit/OpReordererTests.cpp | 32 |
12 files changed, 219 insertions, 25 deletions
diff --git a/libs/hwui/BakedOpDispatcher.cpp b/libs/hwui/BakedOpDispatcher.cpp index 9e39797270a5..a892b1b29174 100644 --- a/libs/hwui/BakedOpDispatcher.cpp +++ b/libs/hwui/BakedOpDispatcher.cpp @@ -705,6 +705,36 @@ void BakedOpDispatcher::onTextOp(BakedOpRenderer& renderer, const TextOp& op, co 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 9c836a00bfea..b12c0c970352 100644 --- a/libs/hwui/BakedOpState.h +++ b/libs/hwui/BakedOpState.h @@ -124,6 +124,15 @@ public: 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 6d90a42d49c7..e1472113d25f 100644 --- a/libs/hwui/OpReorderer.cpp +++ b/libs/hwui/OpReorderer.cpp @@ -90,7 +90,9 @@ public: } 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 @@ public: 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 @@ void OpReorderer::LayerReorderer::replayBakedOpsImpl(void* arg, 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 @@ void OpReorderer::deferSimpleRectsOp(const SimpleRectsOp& op) { 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::deferTextOp(const TextOp& op) { } } +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 b58b7744c7c3..bb230054ad15 100644 --- a/libs/hwui/RecordedOp.h +++ b/libs/hwui/RecordedOp.h @@ -62,6 +62,7 @@ struct Vertex; 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 @@ struct TextOp : RecordedOp { 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 7f035e549404..46ae7904ae78 100644 --- a/libs/hwui/RecordingCanvas.cpp +++ b/libs/hwui/RecordingCanvas.cpp @@ -503,9 +503,15 @@ void RecordingCanvas::drawText(const uint16_t* glyphs, const float* positions, i 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 49bdba8bec9d..ce7d13507aea 100644 --- a/libs/hwui/RecordingCanvas.h +++ b/libs/hwui/RecordingCanvas.h @@ -189,10 +189,10 @@ public: 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 6cef85203352..4c8d23dd82ea 100644 --- a/libs/hwui/tests/common/TestUtils.cpp +++ b/libs/hwui/tests/common/TestUtils.cpp @@ -41,11 +41,8 @@ void TestUtils::drawTextToCanvas(TestCanvas* canvas, const char* text, // 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 @@ void TestUtils::drawTextToCanvas(TestCanvas* canvas, const char* text, 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 0af99398b5f5..4f84474f9988 100644 --- a/libs/hwui/tests/common/TestUtils.h +++ b/libs/hwui/tests/common/TestUtils.h @@ -191,6 +191,9 @@ public: 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 082c6287e86f..e56f2f97007e 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 @@ class OvalAnimation : public TestScene { 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 000000000000..1823db2940aa --- /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 33eff5ba2da0..f9f5316a58df 100644 --- a/libs/hwui/tests/unit/BakedOpStateTests.cpp +++ b/libs/hwui/tests/unit/BakedOpStateTests.cpp @@ -56,6 +56,28 @@ TEST(ResolvedRenderState, construct) { } } +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 ac356a47c87c..ab0cc878178c 100644 --- a/libs/hwui/tests/unit/OpReordererTests.cpp +++ b/libs/hwui/tests/unit/OpReordererTests.cpp @@ -221,7 +221,35 @@ TEST(OpReorderer, simpleBatching) { << "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 @@ TEST(OpReorderer, textStrikethroughBatching) { 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) { |