Add TextOnPath support to new reorderer/renderer
bug:22480459 Change-Id: I302048ec09901420c15003e21e44a551cc59c7ad
This commit is contained in:
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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());
|
||||
|
||||
60
libs/hwui/tests/common/scenes/TextAnimation.cpp
Normal file
60
libs/hwui/tests/common/scenes/TextAnimation.cpp
Normal 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);
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user