Add TextOnPath support to new reorderer/renderer

bug:22480459

Change-Id: I302048ec09901420c15003e21e44a551cc59c7ad
This commit is contained in:
Chris Craik
2015-12-15 10:34:36 -08:00
parent 20754c5a11
commit d7448e65e2
12 changed files with 219 additions and 25 deletions

View File

@@ -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;

View File

@@ -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;

View File

@@ -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,

View File

@@ -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
////////////////////////////////////////////////////////////////////////////////////////////////////

View File

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

View File

@@ -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; }

View File

@@ -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 */

View File

@@ -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();

View File

@@ -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());

View File

@@ -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);
}
};

View File

@@ -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

View File

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