Initial text support in new reorderer/renderer
Removes obsolete drawPosText codepath, and unifies text decoration behavior.
Change-Id: I9c563249ab688a3394445a0e7fe1b9d0661f6f7c
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 0a57d50..cc68fb2 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -37,6 +37,7 @@
AnimatorManager.cpp \
AssetAtlas.cpp \
Caches.cpp \
+ Canvas.cpp \
CanvasState.cpp \
ClipArea.cpp \
DamageAccumulator.cpp \
diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp
index d2d3285..d13d7ef 100644
--- a/libs/hwui/BakedOpRenderer.cpp
+++ b/libs/hwui/BakedOpRenderer.cpp
@@ -24,6 +24,9 @@
#include "utils/GLUtils.h"
#include "VertexBuffer.h"
+#include <algorithm>
+#include <math.h>
+
namespace android {
namespace uirenderer {
@@ -183,6 +186,10 @@
renderer.renderGlop(state, glop);
}
+void BakedOpDispatcher::onLinesOp(BakedOpRenderer& renderer, const LinesOp& op, const BakedOpState& state) {
+ LOG_ALWAYS_FATAL("todo");
+}
+
void BakedOpDispatcher::onRectOp(BakedOpRenderer& renderer, const RectOp& op, const BakedOpState& state) {
Glop glop;
GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
@@ -270,6 +277,91 @@
renderer.renderGlop(state, glop);
}
+static void renderTextShadow(BakedOpRenderer& renderer, FontRenderer& fontRenderer,
+ const TextOp& op, const BakedOpState& state) {
+ renderer.caches().textureState().activateTexture(0);
+
+ PaintUtils::TextShadow textShadow;
+ if (!PaintUtils::getTextShadow(op.paint, &textShadow)) {
+ LOG_ALWAYS_FATAL("failed to query shadow attributes");
+ }
+
+ renderer.caches().dropShadowCache.setFontRenderer(fontRenderer);
+ ShadowTexture* texture = renderer.caches().dropShadowCache.get(
+ op.paint, (const char*) op.glyphs,
+ op.glyphCount, textShadow.radius, op.positions);
+ // If the drop shadow exceeds the max texture size or couldn't be
+ // allocated, skip drawing
+ if (!texture) return;
+ const AutoTexture autoCleanup(texture);
+
+ const float sx = op.x - texture->left + textShadow.dx;
+ const float sy = op.y - texture->top + textShadow.dy;
+
+ Glop glop;
+ GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
+ .setRoundRectClipState(state.roundRectClipState)
+ .setMeshTexturedUnitQuad(nullptr)
+ .setFillShadowTexturePaint(*texture, textShadow.color, *op.paint, state.alpha)
+ .setTransform(state.computedState.transform, TransformFlags::None)
+ .setModelViewMapUnitToRect(Rect(sx, sy, sx + texture->width, sy + texture->height))
+ .build();
+ renderer.renderGlop(state, glop);
+}
+
+void BakedOpDispatcher::onTextOp(BakedOpRenderer& renderer, const TextOp& op, const BakedOpState& state) {
+ FontRenderer& fontRenderer = renderer.caches().fontRenderer.getFontRenderer();
+
+ if (CC_UNLIKELY(PaintUtils::hasTextShadow(op.paint))) {
+ fontRenderer.setFont(op.paint, SkMatrix::I());
+ renderTextShadow(renderer, fontRenderer, op, state);
+ }
+
+ float x = op.x;
+ float y = op.y;
+ const Matrix4& transform = state.computedState.transform;
+ const bool pureTranslate = transform.isPureTranslate();
+ if (CC_LIKELY(pureTranslate)) {
+ x = floorf(x + transform.getTranslateX() + 0.5f);
+ y = floorf(y + transform.getTranslateY() + 0.5f);
+ fontRenderer.setFont(op.paint, SkMatrix::I());
+ fontRenderer.setTextureFiltering(false);
+ } else if (CC_UNLIKELY(transform.isPerspective())) {
+ fontRenderer.setFont(op.paint, SkMatrix::I());
+ fontRenderer.setTextureFiltering(true);
+ } else {
+ // We only pass a partial transform to the font renderer. That partial
+ // matrix defines how glyphs are rasterized. Typically we want glyphs
+ // to be rasterized at their final size on screen, which means the partial
+ // matrix needs to take the scale factor into account.
+ // When a partial matrix is used to transform glyphs during rasterization,
+ // the mesh is generated with the inverse transform (in the case of scale,
+ // the mesh is generated at 1.0 / scale for instance.) This allows us to
+ // apply the full transform matrix at draw time in the vertex shader.
+ // Applying the full matrix in the shader is the easiest way to handle
+ // rotation and perspective and allows us to always generated quads in the
+ // font renderer which greatly simplifies the code, clipping in particular.
+ float sx, sy;
+ transform.decomposeScale(sx, sy);
+ fontRenderer.setFont(op.paint, SkMatrix::MakeScale(
+ roundf(std::max(1.0f, sx)),
+ roundf(std::max(1.0f, sy))));
+ fontRenderer.setTextureFiltering(true);
+ }
+
+ // TODO: Implement better clipping for scaled/rotated text
+ const Rect* clip = !pureTranslate ? nullptr : &state.computedState.clipRect;
+ 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, x, y, pureTranslate, alpha, mode, op.paint);
+
+ bool hasActiveLayer = false; // TODO
+ fontRenderer.renderPosText(op.paint, clip, (const char*) op.glyphs, op.glyphCount, x, y,
+ op.positions, hasActiveLayer ? &layerBounds : nullptr, &functor, true); // TODO: merging
+}
+
void BakedOpDispatcher::onLayerOp(BakedOpRenderer& renderer, const LayerOp& op, const BakedOpState& state) {
OffscreenBuffer* buffer = *op.layerHandle;
diff --git a/libs/hwui/Canvas.cpp b/libs/hwui/Canvas.cpp
new file mode 100644
index 0000000..bc88c81
--- /dev/null
+++ b/libs/hwui/Canvas.cpp
@@ -0,0 +1,56 @@
+/*
+ * 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 "Canvas.h"
+
+#include <SkDrawFilter.h>
+
+namespace android {
+
+void Canvas::drawTextDecorations(float x, float y, float length, const SkPaint& paint) {
+ uint32_t flags;
+ SkDrawFilter* drawFilter = getDrawFilter();
+ if (drawFilter) {
+ SkPaint paintCopy(paint);
+ drawFilter->filter(&paintCopy, SkDrawFilter::kText_Type);
+ flags = paintCopy.getFlags();
+ } else {
+ flags = paint.getFlags();
+ }
+ if (flags & (SkPaint::kUnderlineText_Flag | SkPaint::kStrikeThruText_Flag)) {
+ // Same values used by Skia
+ const float kStdStrikeThru_Offset = (-6.0f / 21.0f);
+ const float kStdUnderline_Offset = (1.0f / 9.0f);
+ const float kStdUnderline_Thickness = (1.0f / 18.0f);
+
+ SkScalar left = x;
+ SkScalar right = x + length;
+ float textSize = paint.getTextSize();
+ float strokeWidth = fmax(textSize * kStdUnderline_Thickness, 1.0f);
+ if (flags & SkPaint::kUnderlineText_Flag) {
+ SkScalar top = y + textSize * kStdUnderline_Offset - 0.5f * strokeWidth;
+ SkScalar bottom = y + textSize * kStdUnderline_Offset + 0.5f * strokeWidth;
+ drawRect(left, top, right, bottom, paint);
+ }
+ if (flags & SkPaint::kStrikeThruText_Flag) {
+ SkScalar top = y + textSize * kStdStrikeThru_Offset - 0.5f * strokeWidth;
+ SkScalar bottom = y + textSize * kStdStrikeThru_Offset + 0.5f * strokeWidth;
+ drawRect(left, top, right, bottom, paint);
+ }
+ }
+}
+
+} // namespace android
diff --git a/libs/hwui/Canvas.h b/libs/hwui/Canvas.h
index 4bd4ac8..b585a27 100644
--- a/libs/hwui/Canvas.h
+++ b/libs/hwui/Canvas.h
@@ -149,16 +149,12 @@
// Text
/**
* drawText: count is of glyphs
- * totalAdvance is ignored in software renderering, used by hardware renderer for
- * text decorations (underlines, strikethroughs).
+ * totalAdvance: used to define width of text decorations (underlines, strikethroughs).
*/
virtual void drawText(const uint16_t* glyphs, const float* positions, int count,
const SkPaint& paint, float x, float y,
float boundsLeft, float boundsTop, float boundsRight, float boundsBottom,
float totalAdvance) = 0;
- /** drawPosText: count is of UTF16 characters, posCount is floats (2 * glyphs) */
- virtual void drawPosText(const uint16_t* text, const float* positions, int count,
- int posCount, const SkPaint& paint) = 0;
/** drawTextOnPath: count is of glyphs */
virtual void drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path,
float hOffset, float vOffset, const SkPaint& paint) = 0;
@@ -171,6 +167,9 @@
* to be added to each glyph's position to get its absolute position.
*/
virtual bool drawTextAbsolutePos() const = 0;
+
+protected:
+ void drawTextDecorations(float x, float y, float length, const SkPaint& paint);
};
}; // namespace android
diff --git a/libs/hwui/DisplayListCanvas.cpp b/libs/hwui/DisplayListCanvas.cpp
index f5e5735..759c12a 100644
--- a/libs/hwui/DisplayListCanvas.cpp
+++ b/libs/hwui/DisplayListCanvas.cpp
@@ -423,18 +423,6 @@
addDrawOp(op);
}
-void DisplayListCanvas::drawPosText(const uint16_t* text, const float* positions,
- int count, int posCount, const SkPaint& paint) {
- if (!text || count <= 0) return;
-
- int bytesCount = 2 * count;
- positions = refBuffer<float>(positions, count * 2);
-
- DrawOp* op = new (alloc()) DrawPosTextOp(refText((const char*) text, bytesCount),
- bytesCount, count, positions, refPaint(&paint));
- addDrawOp(op);
-}
-
void DisplayListCanvas::drawText(const uint16_t* glyphs, const float* positions,
int count, const SkPaint& paint, float x, float y,
float boundsLeft, float boundsTop, float boundsRight, float boundsBottom,
@@ -450,6 +438,7 @@
DrawOp* op = new (alloc()) DrawTextOp(text, bytesCount, count,
x, y, positions, refPaint(&paint), totalAdvance, bounds);
addDrawOp(op);
+ drawTextDecorations(x, y, totalAdvance, paint);
}
void DisplayListCanvas::drawRegion(const SkRegion& region, const SkPaint& paint) {
diff --git a/libs/hwui/DisplayListCanvas.h b/libs/hwui/DisplayListCanvas.h
index 609103b..bf98f79d 100644
--- a/libs/hwui/DisplayListCanvas.h
+++ b/libs/hwui/DisplayListCanvas.h
@@ -212,8 +212,6 @@
virtual void drawText(const uint16_t* glyphs, const float* positions, int count,
const SkPaint& paint, float x, float y, float boundsLeft, float boundsTop,
float boundsRight, float boundsBottom, float totalAdvance) override;
- virtual void drawPosText(const uint16_t* text, const float* positions, int count,
- int posCount, const SkPaint& paint) override;
virtual void drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path,
float hOffset, float vOffset, const SkPaint& paint) override;
virtual bool drawTextAbsolutePos() const override { return false; }
diff --git a/libs/hwui/DisplayListOp.h b/libs/hwui/DisplayListOp.h
index 772aa72..977b53c 100644
--- a/libs/hwui/DisplayListOp.h
+++ b/libs/hwui/DisplayListOp.h
@@ -1278,24 +1278,6 @@
float mVOffset;
};
-class DrawPosTextOp : public DrawSomeTextOp {
-public:
- DrawPosTextOp(const char* text, int bytesCount, int count,
- const float* positions, const SkPaint* paint)
- : DrawSomeTextOp(text, bytesCount, count, paint), mPositions(positions) {
- /* TODO: inherit from DrawBounded and init mLocalBounds */
- }
-
- virtual void applyDraw(OpenGLRenderer& renderer, Rect& dirty) override {
- renderer.drawPosText(mText, mBytesCount, mCount, mPositions, mPaint);
- }
-
- virtual const char* name() override { return "DrawPosText"; }
-
-private:
- const float* mPositions;
-};
-
class DrawTextOp : public DrawStrokableOp {
public:
DrawTextOp(const char* text, int bytesCount, int count, float x, float y,
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
index ccf0b48..5f33cae 100644
--- a/libs/hwui/FontRenderer.cpp
+++ b/libs/hwui/FontRenderer.cpp
@@ -21,13 +21,20 @@
#include "Extensions.h"
#include "Glop.h"
#include "GlopBuilder.h"
-#include "OpenGLRenderer.h"
#include "PixelBuffer.h"
#include "Rect.h"
#include "renderstate/RenderState.h"
#include "utils/Blur.h"
#include "utils/Timing.h"
+
+#if HWUI_NEW_OPS
+#include "BakedOpState.h"
+#include "BakedOpRenderer.h"
+#else
+#include "OpenGLRenderer.h"
+#endif
+
#include <algorithm>
#include <cutils/properties.h>
#include <SkGlyph.h>
@@ -59,14 +66,25 @@
int transformFlags = pureTranslate
? TransformFlags::MeshIgnoresCanvasTransform : TransformFlags::None;
Glop glop;
+#if HWUI_NEW_OPS
+ GlopBuilder(renderer->renderState(), renderer->caches(), &glop)
+ .setRoundRectClipState(bakedState->roundRectClipState)
+ .setMeshTexturedIndexedQuads(texture.mesh(), texture.meshElementCount())
+ .setFillTexturePaint(texture.getTexture(), textureFillFlags, paint, bakedState->alpha)
+ .setTransform(bakedState->computedState.transform, transformFlags)
+ .setModelViewOffsetRect(0, 0, Rect(0, 0, 0, 0))
+ .build();
+ renderer->renderGlop(*bakedState, glop);
+#else
GlopBuilder(renderer->mRenderState, renderer->mCaches, &glop)
+ .setRoundRectClipState(renderer->currentSnapshot()->roundRectClipState)
.setMeshTexturedIndexedQuads(texture.mesh(), texture.meshElementCount())
.setFillTexturePaint(texture.getTexture(), textureFillFlags, paint, renderer->currentSnapshot()->alpha)
.setTransform(*(renderer->currentSnapshot()), transformFlags)
.setModelViewOffsetRect(0, 0, Rect(0, 0, 0, 0))
- .setRoundRectClipState(renderer->currentSnapshot()->roundRectClipState)
.build();
renderer->renderGlop(glop);
+#endif
}
///////////////////////////////////////////////////////////////////////////////
@@ -539,7 +557,7 @@
}
FontRenderer::DropShadow FontRenderer::renderDropShadow(const SkPaint* paint, const char *text,
- uint32_t startIndex, uint32_t len, int numGlyphs, float radius, const float* positions) {
+ int numGlyphs, float radius, const float* positions) {
checkInit();
DropShadow image;
@@ -558,7 +576,7 @@
mBounds = nullptr;
Rect bounds;
- mCurrentFont->measure(paint, text, startIndex, len, numGlyphs, &bounds, positions);
+ mCurrentFont->measure(paint, text, numGlyphs, &bounds, positions);
uint32_t intRadius = Blur::convertRadiusToInt(radius);
uint32_t paddedWidth = (uint32_t) (bounds.right - bounds.left) + 2 * intRadius;
@@ -590,7 +608,7 @@
// text has non-whitespace, so draw and blur to create the shadow
// NOTE: bounds.isEmpty() can't be used here, since vertical coordinates are inverted
// TODO: don't draw pure whitespace in the first place, and avoid needing this check
- mCurrentFont->render(paint, text, startIndex, len, numGlyphs, penX, penY,
+ mCurrentFont->render(paint, text, numGlyphs, penX, penY,
Font::BITMAP, dataBuffer, paddedWidth, paddedHeight, nullptr, positions);
// Unbind any PBO we might have used
@@ -635,15 +653,15 @@
}
bool FontRenderer::renderPosText(const SkPaint* paint, const Rect* clip, const char *text,
- uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y,
- const float* positions, Rect* bounds, TextDrawFunctor* functor, bool forceFinish) {
+ int numGlyphs, int x, int y, const float* positions,
+ Rect* bounds, TextDrawFunctor* functor, bool forceFinish) {
if (!mCurrentFont) {
ALOGE("No font set");
return false;
}
initRender(clip, bounds, functor);
- mCurrentFont->render(paint, text, startIndex, len, numGlyphs, x, y, positions);
+ mCurrentFont->render(paint, text, numGlyphs, x, y, positions);
if (forceFinish) {
finishRender();
@@ -653,15 +671,15 @@
}
bool FontRenderer::renderTextOnPath(const SkPaint* paint, const Rect* clip, const char *text,
- uint32_t startIndex, uint32_t len, int numGlyphs, const SkPath* path,
- float hOffset, float vOffset, Rect* bounds, TextDrawFunctor* functor) {
+ int numGlyphs, const SkPath* path, float hOffset, float vOffset,
+ Rect* bounds, TextDrawFunctor* functor) {
if (!mCurrentFont) {
ALOGE("No font set");
return false;
}
initRender(clip, bounds, functor);
- mCurrentFont->render(paint, text, startIndex, len, numGlyphs, path, hOffset, vOffset);
+ mCurrentFont->render(paint, text, numGlyphs, path, hOffset, vOffset);
finishRender();
return mDrawn;
diff --git a/libs/hwui/FontRenderer.h b/libs/hwui/FontRenderer.h
index 8172312..87cfe7f 100644
--- a/libs/hwui/FontRenderer.h
+++ b/libs/hwui/FontRenderer.h
@@ -44,13 +44,28 @@
namespace android {
namespace uirenderer {
+#if HWUI_NEW_OPS
+class BakedOpState;
+class BakedOpRenderer;
+#else
class OpenGLRenderer;
+#endif
class TextDrawFunctor {
public:
- TextDrawFunctor(OpenGLRenderer* renderer, float x, float y, bool pureTranslate,
+ TextDrawFunctor(
+#if HWUI_NEW_OPS
+ BakedOpRenderer* renderer,
+ const BakedOpState* bakedState,
+#else
+ OpenGLRenderer* renderer,
+#endif
+ float x, float y, bool pureTranslate,
int alpha, SkXfermode::Mode mode, const SkPaint* paint)
: renderer(renderer)
+#if HWUI_NEW_OPS
+ , bakedState(bakedState)
+#endif
, x(x)
, y(y)
, pureTranslate(pureTranslate)
@@ -61,7 +76,12 @@
void draw(CacheTexture& texture, bool linearFiltering);
+#if HWUI_NEW_OPS
+ BakedOpRenderer* renderer;
+ const BakedOpState* bakedState;
+#else
OpenGLRenderer* renderer;
+#endif
float x;
float y;
bool pureTranslate;
@@ -83,15 +103,13 @@
void precache(const SkPaint* paint, const char* text, int numGlyphs, const SkMatrix& matrix);
void endPrecaching();
- // bounds is an out parameter
bool renderPosText(const SkPaint* paint, const Rect* clip, const char *text,
- uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y, const float* positions,
- Rect* bounds, TextDrawFunctor* functor, bool forceFinish = true);
+ int numGlyphs, int x, int y, const float* positions,
+ Rect* outBounds, TextDrawFunctor* functor, bool forceFinish = true);
- // bounds is an out parameter
bool renderTextOnPath(const SkPaint* paint, const Rect* clip, const char *text,
- uint32_t startIndex, uint32_t len, int numGlyphs, const SkPath* path,
- float hOffset, float vOffset, Rect* bounds, TextDrawFunctor* functor);
+ int numGlyphs, const SkPath* path,
+ float hOffset, float vOffset, Rect* outBounds, TextDrawFunctor* functor);
struct DropShadow {
uint32_t width;
@@ -103,8 +121,8 @@
// After renderDropShadow returns, the called owns the memory in DropShadow.image
// and is responsible for releasing it when it's done with it
- DropShadow renderDropShadow(const SkPaint* paint, const char *text, uint32_t startIndex,
- uint32_t len, int numGlyphs, float radius, const float* positions);
+ DropShadow renderDropShadow(const SkPaint* paint, const char *text, int numGlyphs,
+ float radius, const float* positions);
void setTextureFiltering(bool linearFiltering) {
mLinearFiltering = linearFiltering;
diff --git a/libs/hwui/OpReorderer.cpp b/libs/hwui/OpReorderer.cpp
index 96cac7e..5e954ae 100644
--- a/libs/hwui/OpReorderer.cpp
+++ b/libs/hwui/OpReorderer.cpp
@@ -671,6 +671,13 @@
currentLayer().deferMergeableOp(mAllocator, bakedStateOp, OpBatchType::Bitmap, mergeId);
}
+void OpReorderer::onLinesOp(const LinesOp& op) {
+ BakedOpState* bakedStateOp = tryBakeOpState(op);
+ if (!bakedStateOp) return; // quick rejected
+ currentLayer().deferUnmergeableOp(mAllocator, bakedStateOp, OpBatchType::Vertices);
+
+}
+
void OpReorderer::onRectOp(const RectOp& op) {
BakedOpState* bakedStateOp = tryBakeOpState(op);
if (!bakedStateOp) return; // quick rejected
@@ -683,6 +690,17 @@
currentLayer().deferUnmergeableOp(mAllocator, bakedStateOp, OpBatchType::Vertices);
}
+void OpReorderer::onTextOp(const TextOp& op) {
+ BakedOpState* bakedStateOp = tryBakeOpState(op);
+ if (!bakedStateOp) 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;
+ mergeid_t mergeId = reinterpret_cast<mergeid_t>(op.paint->getColor());
+ currentLayer().deferMergeableOp(mAllocator, bakedStateOp, batchId, mergeId);
+}
+
void OpReorderer::saveForLayer(uint32_t layerWidth, uint32_t layerHeight,
float contentTranslateX, float contentTranslateY,
const Rect& repaintRect,
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 12c4607..e386b1c 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -1950,7 +1950,7 @@
}
void OpenGLRenderer::drawTextShadow(const SkPaint* paint, const char* text,
- int bytesCount, int count, const float* positions,
+ int count, const float* positions,
FontRenderer& fontRenderer, int alpha, float x, float y) {
mCaches.textureState().activateTexture(0);
@@ -1963,7 +1963,7 @@
// if shader-based correction is enabled
mCaches.dropShadowCache.setFontRenderer(fontRenderer);
ShadowTexture* texture = mCaches.dropShadowCache.get(
- paint, text, bytesCount, count, textShadow.radius, positions);
+ paint, text, count, textShadow.radius, positions);
// If the drop shadow exceeds the max texture size or couldn't be
// allocated, skip drawing
if (!texture) return;
@@ -1991,57 +1991,6 @@
&& PaintUtils::getXfermode(paint->getXfermode()) == SkXfermode::kSrcOver_Mode;
}
-void OpenGLRenderer::drawPosText(const char* text, int bytesCount, int count,
- const float* positions, const SkPaint* paint) {
- if (text == nullptr || count == 0 || mState.currentlyIgnored() || canSkipText(paint)) {
- return;
- }
-
- // NOTE: Skia does not support perspective transform on drawPosText yet
- if (!currentTransform()->isSimple()) {
- return;
- }
-
- mRenderState.scissor().setEnabled(true);
-
- float x = 0.0f;
- float y = 0.0f;
- const bool pureTranslate = currentTransform()->isPureTranslate();
- if (pureTranslate) {
- x = floorf(x + currentTransform()->getTranslateX() + 0.5f);
- y = floorf(y + currentTransform()->getTranslateY() + 0.5f);
- }
-
- FontRenderer& fontRenderer = mCaches.fontRenderer.getFontRenderer();
- fontRenderer.setFont(paint, SkMatrix::I());
-
- int alpha = PaintUtils::getAlphaDirect(paint) * currentSnapshot()->alpha;
- SkXfermode::Mode mode = PaintUtils::getXfermodeDirect(paint);
-
- if (CC_UNLIKELY(PaintUtils::hasTextShadow(paint))) {
- drawTextShadow(paint, text, bytesCount, count, positions, fontRenderer,
- alpha, 0.0f, 0.0f);
- }
-
- // Pick the appropriate texture filtering
- bool linearFilter = currentTransform()->changesBounds();
- if (pureTranslate && !linearFilter) {
- linearFilter = fabs(y - (int) y) > 0.0f || fabs(x - (int) x) > 0.0f;
- }
- fontRenderer.setTextureFiltering(linearFilter);
-
- const Rect& clip(pureTranslate ? writableSnapshot()->getRenderTargetClip() : writableSnapshot()->getLocalClip());
- Rect bounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
-
- TextDrawFunctor functor(this, x, y, pureTranslate, alpha, mode, paint);
- if (fontRenderer.renderPosText(paint, &clip, text, 0, bytesCount, count, x, y,
- positions, hasLayer() ? &bounds : nullptr, &functor)) {
- dirtyLayer(bounds.left, bounds.top, bounds.right, bounds.bottom, *currentTransform());
- mDirty = true;
- }
-
-}
-
bool OpenGLRenderer::findBestFontTransform(const mat4& transform, SkMatrix* outMatrix) const {
if (CC_LIKELY(transform.isPureTranslate())) {
outMatrix->setIdentity();
@@ -2166,7 +2115,7 @@
if (CC_UNLIKELY(PaintUtils::hasTextShadow(paint))) {
fontRenderer.setFont(paint, SkMatrix::I());
- drawTextShadow(paint, text, bytesCount, count, positions, fontRenderer,
+ drawTextShadow(paint, text, count, positions, fontRenderer,
alpha, oldX, oldY);
}
@@ -2195,17 +2144,22 @@
Rect layerBounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
bool status;
+#if HWUI_NEW_OPS
+ LOG_ALWAYS_FATAL("unsupported");
+ TextDrawFunctor functor(nullptr, nullptr, x, y, pureTranslate, alpha, mode, paint);
+#else
TextDrawFunctor functor(this, x, y, pureTranslate, alpha, mode, paint);
+#endif
// don't call issuedrawcommand, do it at end of batch
bool forceFinish = (drawOpMode != DrawOpMode::kDefer);
if (CC_UNLIKELY(paint->getTextAlign() != SkPaint::kLeft_Align)) {
SkPaint paintCopy(*paint);
paintCopy.setTextAlign(SkPaint::kLeft_Align);
- status = fontRenderer.renderPosText(&paintCopy, clip, text, 0, bytesCount, count, x, y,
+ status = fontRenderer.renderPosText(&paintCopy, clip, text, count, x, y,
positions, hasActiveLayer ? &layerBounds : nullptr, &functor, forceFinish);
} else {
- status = fontRenderer.renderPosText(paint, clip, text, 0, bytesCount, count, x, y,
+ status = fontRenderer.renderPosText(paint, clip, text, count, x, y,
positions, hasActiveLayer ? &layerBounds : nullptr, &functor, forceFinish);
}
@@ -2216,8 +2170,6 @@
dirtyLayerUnchecked(layerBounds, getRegion());
}
- drawTextDecorations(totalAdvance, oldX, oldY, paint);
-
mDirty = true;
}
@@ -2236,12 +2188,17 @@
int alpha = PaintUtils::getAlphaDirect(paint) * currentSnapshot()->alpha;
SkXfermode::Mode mode = PaintUtils::getXfermodeDirect(paint);
+#if HWUI_NEW_OPS
+ LOG_ALWAYS_FATAL("unsupported");
+ TextDrawFunctor functor(nullptr, nullptr, 0.0f, 0.0f, false, alpha, mode, paint);
+#else
TextDrawFunctor functor(this, 0.0f, 0.0f, false, alpha, mode, paint);
+#endif
const Rect* clip = &writableSnapshot()->getLocalClip();
Rect bounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
- if (fontRenderer.renderTextOnPath(paint, clip, text, 0, bytesCount, count, path,
+ if (fontRenderer.renderTextOnPath(paint, clip, text, count, path,
hOffset, vOffset, hasLayer() ? &bounds : nullptr, &functor)) {
dirtyLayer(bounds.left, bounds.top, bounds.right, bounds.bottom, *currentTransform());
mDirty = true;
@@ -2375,56 +2332,6 @@
renderGlop(glop);
}
-// Same values used by Skia
-#define kStdStrikeThru_Offset (-6.0f / 21.0f)
-#define kStdUnderline_Offset (1.0f / 9.0f)
-#define kStdUnderline_Thickness (1.0f / 18.0f)
-
-void OpenGLRenderer::drawTextDecorations(float underlineWidth, float x, float y,
- const SkPaint* paint) {
- // Handle underline and strike-through
- uint32_t flags = paint->getFlags();
- if (flags & (SkPaint::kUnderlineText_Flag | SkPaint::kStrikeThruText_Flag)) {
- SkPaint paintCopy(*paint);
-
- if (CC_LIKELY(underlineWidth > 0.0f)) {
- const float textSize = paintCopy.getTextSize();
- const float strokeWidth = std::max(textSize * kStdUnderline_Thickness, 1.0f);
-
- const float left = x;
- float top = 0.0f;
-
- int linesCount = 0;
- if (flags & SkPaint::kUnderlineText_Flag) linesCount++;
- if (flags & SkPaint::kStrikeThruText_Flag) linesCount++;
-
- const int pointsCount = 4 * linesCount;
- float points[pointsCount];
- int currentPoint = 0;
-
- if (flags & SkPaint::kUnderlineText_Flag) {
- top = y + textSize * kStdUnderline_Offset;
- points[currentPoint++] = left;
- points[currentPoint++] = top;
- points[currentPoint++] = left + underlineWidth;
- points[currentPoint++] = top;
- }
-
- if (flags & SkPaint::kStrikeThruText_Flag) {
- top = y + textSize * kStdStrikeThru_Offset;
- points[currentPoint++] = left;
- points[currentPoint++] = top;
- points[currentPoint++] = left + underlineWidth;
- points[currentPoint++] = top;
- }
-
- paintCopy.setStrokeWidth(strokeWidth);
-
- drawLines(&points[0], pointsCount, &paintCopy);
- }
- }
-}
-
void OpenGLRenderer::drawRects(const float* rects, int count, const SkPaint* paint) {
if (mState.currentlyIgnored()) {
return;
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index 400c225..84bc9b0 100755
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -193,8 +193,6 @@
void drawPoints(const float* points, int count, const SkPaint* paint);
void drawTextOnPath(const char* text, int bytesCount, int count, const SkPath* path,
float hOffset, float vOffset, const SkPaint* paint);
- void drawPosText(const char* text, int bytesCount, int count,
- const float* positions, const SkPaint* paint);
void drawText(const char* text, int bytesCount, int count, float x, float y,
const float* positions, const SkPaint* paint, float totalAdvance, const Rect& bounds,
DrawOpMode drawOpMode = DrawOpMode::kImmediate);
@@ -637,24 +635,11 @@
*/
void drawConvexPath(const SkPath& path, const SkPaint* paint);
- /**
- * Draws text underline and strike-through if needed.
- *
- * @param text The text to decor
- * @param bytesCount The number of bytes in the text
- * @param totalAdvance The total advance in pixels, defines underline/strikethrough length
- * @param x The x coordinate where the text will be drawn
- * @param y The y coordinate where the text will be drawn
- * @param paint The paint to draw the text with
- */
- void drawTextDecorations(float totalAdvance, float x, float y, const SkPaint* paint);
-
/**
* Draws shadow layer on text (with optional positions).
*
* @param paint The paint to draw the shadow with
* @param text The text to draw
- * @param bytesCount The number of bytes in the text
* @param count The number of glyphs in the text
* @param positions The x, y positions of individual glyphs (or NULL)
* @param fontRenderer The font renderer object
@@ -662,7 +647,7 @@
* @param x The x coordinate where the shadow will be drawn
* @param y The y coordinate where the shadow will be drawn
*/
- void drawTextShadow(const SkPaint* paint, const char* text, int bytesCount, int count,
+ void drawTextShadow(const SkPaint* paint, const char* text, int count,
const float* positions, FontRenderer& fontRenderer, int alpha,
float x, float y);
diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h
index ef05367..127dca5 100644
--- a/libs/hwui/RecordedOp.h
+++ b/libs/hwui/RecordedOp.h
@@ -17,6 +17,7 @@
#ifndef ANDROID_HWUI_RECORDED_OP_H
#define ANDROID_HWUI_RECORDED_OP_H
+#include "font/FontUtil.h"
#include "Matrix.h"
#include "Rect.h"
#include "RenderNode.h"
@@ -42,10 +43,12 @@
*/
#define MAP_OPS(OP_FN) \
OP_FN(BitmapOp) \
+ OP_FN(LinesOp) \
OP_FN(RectOp) \
OP_FN(RenderNodeOp) \
OP_FN(ShadowOp) \
OP_FN(SimpleRectsOp) \
+ OP_FN(TextOp) \
OP_FN(BeginLayerOp) \
OP_FN(EndLayerOp) \
OP_FN(LayerOp)
@@ -98,6 +101,10 @@
bool skipInOrderDraw = false;
};
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// Standard Ops
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
struct BitmapOp : RecordedOp {
BitmapOp(BASE_PARAMS, const SkBitmap* bitmap)
: SUPER(BitmapOp)
@@ -106,6 +113,15 @@
// TODO: asset atlas/texture id lookup?
};
+struct LinesOp : RecordedOp {
+ LinesOp(BASE_PARAMS, const float* points, const int floatCount)
+ : SUPER(LinesOp)
+ , points(points)
+ , floatCount(floatCount) {}
+ const float* points;
+ const int floatCount;
+};
+
struct RectOp : RecordedOp {
RectOp(BASE_PARAMS)
: SUPER(RectOp) {}
@@ -148,6 +164,27 @@
const size_t vertexCount;
};
+struct TextOp : RecordedOp {
+ TextOp(BASE_PARAMS, const glyph_t* glyphs, const float* positions, int glyphCount,
+ float x, float y)
+ : SUPER(TextOp)
+ , glyphs(glyphs)
+ , positions(positions)
+ , glyphCount(glyphCount)
+ , x(x)
+ , y(y) {}
+ const glyph_t* glyphs;
+ const float* positions;
+ const int glyphCount;
+ const float x;
+ const float y;
+};
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// Layers
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
/**
* Stateful operation! denotes the creation of an off-screen layer,
* and that commands following will render into it.
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 6ab253c..61fa384 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -230,12 +230,9 @@
void RecordingCanvas::drawPaint(const SkPaint& paint) {
// TODO: more efficient recording?
- Matrix4 identity;
- identity.loadIdentity();
-
addOp(new (alloc()) RectOp(
mState.getRenderTargetClipBounds(),
- identity,
+ Matrix4::identity(),
mState.getRenderTargetClipBounds(),
refPaint(&paint)));
}
@@ -244,9 +241,30 @@
void RecordingCanvas::drawPoints(const float* points, int count, const SkPaint& paint) {
LOG_ALWAYS_FATAL("TODO!");
}
-void RecordingCanvas::drawLines(const float* points, int count, const SkPaint& paint) {
- LOG_ALWAYS_FATAL("TODO!");
+
+void RecordingCanvas::drawLines(const float* points, int floatCount, const SkPaint& paint) {
+ if (floatCount < 4) return;
+ floatCount &= ~0x3; // round down to nearest four
+
+ Rect unmappedBounds(points[0], points[1], points[0], points[1]);
+ for (int i = 2; i < floatCount; i += 2) {
+ unmappedBounds.left = std::min(unmappedBounds.left, points[i]);
+ unmappedBounds.right = std::max(unmappedBounds.right, points[i]);
+ unmappedBounds.top = std::min(unmappedBounds.top, points[i + 1]);
+ unmappedBounds.bottom = std::max(unmappedBounds.bottom, points[i + 1]);
+ }
+
+ // since anything AA stroke with less than 1.0 pixel width is drawn with an alpha-reduced
+ // 1.0 stroke, treat 1.0 as minimum.
+ unmappedBounds.outset(std::max(paint.getStrokeWidth(), 1.0f) * 0.5f);
+
+ addOp(new (alloc()) LinesOp(
+ unmappedBounds,
+ *mState.currentSnapshot()->transform,
+ mState.getRenderTargetClipBounds(),
+ refPaint(&paint), refBuffer<float>(points, floatCount), floatCount));
}
+
void RecordingCanvas::drawRect(float left, float top, float right, float bottom, const SkPaint& paint) {
addOp(new (alloc()) RectOp(
Rect(left, top, right, bottom),
@@ -388,17 +406,24 @@
}
// Text
-void RecordingCanvas::drawText(const uint16_t* glyphs, const float* positions, int count,
+void RecordingCanvas::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) {
- LOG_ALWAYS_FATAL("TODO!");
+ if (!glyphs || !positions || glyphCount <= 0 || PaintUtils::paintWillNotDrawText(paint)) return;
+ glyphs = refBuffer<glyph_t>(glyphs, glyphCount);
+ positions = refBuffer<float>(positions, glyphCount * 2);
+
+ addOp(new (alloc()) TextOp(
+ Rect(boundsLeft, boundsTop, boundsRight, boundsBottom),
+ *(mState.currentSnapshot()->transform),
+ mState.getRenderTargetClipBounds(),
+ refPaint(&paint), glyphs, positions, glyphCount, x, y));
+ drawTextDecorations(x, y, totalAdvance, paint);
}
-void RecordingCanvas::drawPosText(const uint16_t* text, const float* positions, int count,
- int posCount, const SkPaint& paint) {
- LOG_ALWAYS_FATAL("TODO!");
-}
+
void RecordingCanvas::drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path,
float hOffset, float vOffset, const SkPaint& paint) {
+ // NOTE: can't use refPaint() directly, since it forces left alignment
LOG_ALWAYS_FATAL("TODO!");
}
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index f26b0c8..736cc9e 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -178,8 +178,6 @@
virtual void drawText(const uint16_t* glyphs, const float* positions, int count,
const SkPaint& paint, float x, float y, float boundsLeft, float boundsTop,
float boundsRight, float boundsBottom, float totalAdvance) override;
- virtual void drawPosText(const uint16_t* text, const float* positions, int count,
- int posCount, const SkPaint& paint) override;
virtual void drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path,
float hOffset, float vOffset, const SkPaint& paint) override;
virtual bool drawTextAbsolutePos() const override { return false; }
@@ -221,6 +219,15 @@
return cachedPath;
}
+ /**
+ * Returns a RenderThread-safe, const copy of the SkPaint parameter passed in (with deduping
+ * based on paint generation ID)
+ *
+ * Note that this forces Left_Align, since drawText glyph rendering expects left alignment,
+ * since alignment offsetting has been done at a higher level. This is done to essentially all
+ * copied paints, since the deduping can mean a paint is shared by drawText commands and other
+ * types (which wouldn't care about alignment).
+ */
inline const SkPaint* refPaint(const SkPaint* paint) {
if (!paint) return nullptr;
@@ -239,10 +246,11 @@
// In the unlikely event that 2 unique paints have the same hash we do a
// object equality check to ensure we don't erroneously dedup them.
if (cachedPaint == nullptr || *cachedPaint != *paint) {
- cachedPaint = new SkPaint(*paint);
- std::unique_ptr<const SkPaint> copy(cachedPaint);
- mDisplayList->paints.push_back(std::move(copy));
+ SkPaint* copy = new SkPaint(*paint);
+ copy->setTextAlign(SkPaint::kLeft_Align);
+ cachedPaint = copy;
+ mDisplayList->paints.emplace_back(copy);
// replaceValueFor() performs an add if the entry doesn't exist
mPaintMap.replaceValueFor(key, cachedPaint);
refBitmapsInShader(cachedPaint->getShader());
diff --git a/libs/hwui/Rect.h b/libs/hwui/Rect.h
index 0736a10..472aad7 100644
--- a/libs/hwui/Rect.h
+++ b/libs/hwui/Rect.h
@@ -260,13 +260,6 @@
bottom = std::max(bottom, y);
}
- void expandToCoverRect(float otherLeft, float otherTop, float otherRight, float otherBottom) {
- left = std::min(left, otherLeft);
- top = std::min(top, otherTop);
- right = std::max(right, otherRight);
- bottom = std::max(bottom, otherBottom);
- }
-
SkRect toSkRect() const {
return SkRect::MakeLTRB(left, top, right, bottom);
}
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 6d3dfac..96c1a7c 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -131,8 +131,6 @@
const SkPaint& paint, float x, float y,
float boundsLeft, float boundsTop, float boundsRight, float boundsBottom,
float totalAdvance) override;
- virtual void drawPosText(const uint16_t* text, const float* positions, int count,
- int posCount, const SkPaint& paint) override;
virtual void drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path,
float hOffset, float vOffset, const SkPaint& paint) override;
@@ -152,7 +150,6 @@
void drawPoints(const float* points, int count, const SkPaint& paint,
SkCanvas::PointMode mode);
- void drawTextDecorations(float x, float y, float length, const SkPaint& paint);
SkAutoTUnref<SkCanvas> mCanvas;
std::unique_ptr<SkDeque> mSaveStack; // lazily allocated, tracks partial saves.
@@ -712,22 +709,7 @@
static_assert(sizeof(SkPoint) == sizeof(float)*2, "SkPoint is no longer two floats");
mCanvas->drawPosText(text, count << 1, reinterpret_cast<const SkPoint*>(positions), paintCopy);
-}
-
-void SkiaCanvas::drawPosText(const uint16_t* text, const float* positions, int count, int posCount,
- const SkPaint& paint) {
- SkPoint* posPtr = posCount > 0 ? new SkPoint[posCount] : NULL;
- int indx;
- for (indx = 0; indx < posCount; indx++) {
- posPtr[indx].fX = positions[indx << 1];
- posPtr[indx].fY = positions[(indx << 1) + 1];
- }
-
- SkPaint paintCopy(paint);
- paintCopy.setTextEncoding(SkPaint::kUTF16_TextEncoding);
- mCanvas->drawPosText(text, count, posPtr, paintCopy);
-
- delete[] posPtr;
+ drawTextDecorations(x, y, totalAdvance, paint);
}
void SkiaCanvas::drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path,
diff --git a/libs/hwui/TextDropShadowCache.cpp b/libs/hwui/TextDropShadowCache.cpp
index b7a76ba..996ac8e 100644
--- a/libs/hwui/TextDropShadowCache.cpp
+++ b/libs/hwui/TextDropShadowCache.cpp
@@ -30,8 +30,7 @@
///////////////////////////////////////////////////////////////////////////////
hash_t ShadowText::hash() const {
- uint32_t charCount = len / sizeof(char16_t);
- uint32_t hash = JenkinsHashMix(0, len);
+ uint32_t hash = JenkinsHashMix(0, glyphCount);
hash = JenkinsHashMix(hash, android::hash_type(radius));
hash = JenkinsHashMix(hash, android::hash_type(textSize));
hash = JenkinsHashMix(hash, android::hash_type(typeface));
@@ -40,10 +39,10 @@
hash = JenkinsHashMix(hash, android::hash_type(scaleX));
if (text) {
hash = JenkinsHashMixShorts(
- hash, reinterpret_cast<const uint16_t*>(text), charCount);
+ hash, reinterpret_cast<const uint16_t*>(text), glyphCount);
}
if (positions) {
- for (uint32_t i = 0; i < charCount * 2; i++) {
+ for (uint32_t i = 0; i < glyphCount * 2; i++) {
hash = JenkinsHashMix(hash, android::hash_type(positions[i]));
}
}
@@ -51,7 +50,7 @@
}
int ShadowText::compare(const ShadowText& lhs, const ShadowText& rhs) {
- int deltaInt = int(lhs.len) - int(rhs.len);
+ int deltaInt = int(lhs.glyphCount) - int(rhs.glyphCount);
if (deltaInt != 0) return deltaInt;
deltaInt = lhs.flags - rhs.flags;
@@ -76,7 +75,7 @@
if (!lhs.text) return -1;
if (!rhs.text) return +1;
- deltaInt = memcmp(lhs.text, rhs.text, lhs.len);
+ deltaInt = memcmp(lhs.text, rhs.text, lhs.glyphCount * sizeof(glyph_t));
if (deltaInt != 0) return deltaInt;
}
@@ -84,7 +83,7 @@
if (!lhs.positions) return -1;
if (!rhs.positions) return +1;
- return memcmp(lhs.positions, rhs.positions, lhs.len << 2);
+ return memcmp(lhs.positions, rhs.positions, lhs.glyphCount << 1);
}
return 0;
@@ -168,16 +167,16 @@
mCache.clear();
}
-ShadowTexture* TextDropShadowCache::get(const SkPaint* paint, const char* text, uint32_t len,
- int numGlyphs, float radius, const float* positions) {
- ShadowText entry(paint, radius, len, text, positions);
+ShadowTexture* TextDropShadowCache::get(const SkPaint* paint, const char* glyphs, int numGlyphs,
+ float radius, const float* positions) {
+ ShadowText entry(paint, radius, numGlyphs * 2, glyphs, positions);
ShadowTexture* texture = mCache.get(entry);
if (!texture) {
SkPaint paintCopy(*paint);
paintCopy.setTextAlign(SkPaint::kLeft_Align);
- FontRenderer::DropShadow shadow = mRenderer->renderDropShadow(&paintCopy, text, 0,
- len, numGlyphs, radius, positions);
+ FontRenderer::DropShadow shadow = mRenderer->renderDropShadow(&paintCopy, glyphs, numGlyphs,
+ radius, positions);
if (!shadow.image) {
return nullptr;
diff --git a/libs/hwui/TextDropShadowCache.h b/libs/hwui/TextDropShadowCache.h
index caf089f..c4f3c5d 100644
--- a/libs/hwui/TextDropShadowCache.h
+++ b/libs/hwui/TextDropShadowCache.h
@@ -34,14 +34,14 @@
class FontRenderer;
struct ShadowText {
- ShadowText(): len(0), radius(0.0f), textSize(0.0f), typeface(nullptr),
+ ShadowText(): glyphCount(0), radius(0.0f), textSize(0.0f), typeface(nullptr),
flags(0), italicStyle(0.0f), scaleX(0), text(nullptr), positions(nullptr) {
}
// len is the number of bytes in text
- ShadowText(const SkPaint* paint, float radius, uint32_t len, const char* srcText,
+ ShadowText(const SkPaint* paint, float radius, uint32_t glyphCount, const char* srcText,
const float* positions):
- len(len), radius(radius), positions(positions) {
+ glyphCount(glyphCount), radius(radius), positions(positions) {
// TODO: Propagate this through the API, we should not cast here
text = (const char16_t*) srcText;
@@ -73,17 +73,16 @@
}
void copyTextLocally() {
- uint32_t charCount = len / sizeof(char16_t);
- str.setTo((const char16_t*) text, charCount);
+ str.setTo((const char16_t*) text, glyphCount);
text = str.string();
if (positions != nullptr) {
positionsCopy.clear();
- positionsCopy.appendArray(positions, charCount * 2);
+ positionsCopy.appendArray(positions, glyphCount * 2);
positions = positionsCopy.array();
}
}
- uint32_t len;
+ uint32_t glyphCount;
float radius;
float textSize;
SkTypeface* typeface;
@@ -136,7 +135,7 @@
*/
void operator()(ShadowText& text, ShadowTexture*& texture) override;
- ShadowTexture* get(const SkPaint* paint, const char* text, uint32_t len,
+ ShadowTexture* get(const SkPaint* paint, const char* text,
int numGlyphs, float radius, const float* positions);
/**
diff --git a/libs/hwui/font/Font.cpp b/libs/hwui/font/Font.cpp
index d680f99..dc82041 100644
--- a/libs/hwui/font/Font.cpp
+++ b/libs/hwui/font/Font.cpp
@@ -291,20 +291,18 @@
return cachedGlyph;
}
-void Font::render(const SkPaint* paint, const char *text, uint32_t start, uint32_t len,
+void Font::render(const SkPaint* paint, const char *text,
int numGlyphs, int x, int y, const float* positions) {
- render(paint, text, start, len, numGlyphs, x, y, FRAMEBUFFER, nullptr,
+ render(paint, text, numGlyphs, x, y, FRAMEBUFFER, nullptr,
0, 0, nullptr, positions);
}
-void Font::render(const SkPaint* paint, const char *text, uint32_t start, uint32_t len,
- int numGlyphs, const SkPath* path, float hOffset, float vOffset) {
- if (numGlyphs == 0 || text == nullptr || len == 0) {
+void Font::render(const SkPaint* paint, const char *text, int numGlyphs,
+ const SkPath* path, float hOffset, float vOffset) {
+ if (numGlyphs == 0 || text == nullptr) {
return;
}
- text += start;
-
int glyphsCount = 0;
SkFixed prevRsbDelta = 0;
@@ -317,7 +315,7 @@
float pathLength = SkScalarToFloat(measure.getLength());
if (paint->getTextAlign() != SkPaint::kLeft_Align) {
- float textWidth = SkScalarToFloat(paint->measureText(text, len));
+ float textWidth = SkScalarToFloat(paint->measureText(text, numGlyphs * 2));
float pathOffset = pathLength;
if (paint->getTextAlign() == SkPaint::kCenter_Align) {
textWidth *= 0.5f;
@@ -347,14 +345,14 @@
}
}
-void Font::measure(const SkPaint* paint, const char* text, uint32_t start, uint32_t len,
+void Font::measure(const SkPaint* paint, const char* text,
int numGlyphs, Rect *bounds, const float* positions) {
if (bounds == nullptr) {
ALOGE("No return rectangle provided to measure text");
return;
}
bounds->set(1e6, -1e6, -1e6, 1e6);
- render(paint, text, start, len, numGlyphs, 0, 0, MEASURE, nullptr, 0, 0, bounds, positions);
+ render(paint, text, numGlyphs, 0, 0, MEASURE, nullptr, 0, 0, bounds, positions);
}
void Font::precache(const SkPaint* paint, const char* text, int numGlyphs) {
@@ -378,10 +376,10 @@
}
}
-void Font::render(const SkPaint* paint, const char* text, uint32_t start, uint32_t len,
+void Font::render(const SkPaint* paint, const char* text,
int numGlyphs, int x, int y, RenderMode mode, uint8_t *bitmap,
uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* positions) {
- if (numGlyphs == 0 || text == nullptr || len == 0) {
+ if (numGlyphs == 0 || text == nullptr) {
return;
}
@@ -395,7 +393,6 @@
};
RenderGlyph render = gRenderGlyph[(mode << 1) + !mIdentityTransform];
- text += start;
int glyphsCount = 0;
while (glyphsCount < numGlyphs) {
diff --git a/libs/hwui/font/Font.h b/libs/hwui/font/Font.h
index 3119d73..59518a1 100644
--- a/libs/hwui/font/Font.h
+++ b/libs/hwui/font/Font.h
@@ -82,10 +82,10 @@
~Font();
- void render(const SkPaint* paint, const char* text, uint32_t start, uint32_t len,
+ void render(const SkPaint* paint, const char* text,
int numGlyphs, int x, int y, const float* positions);
- void render(const SkPaint* paint, const char* text, uint32_t start, uint32_t len,
+ void render(const SkPaint* paint, const char* text,
int numGlyphs, const SkPath* path, float hOffset, float vOffset);
const Font::FontDescription& getDescription() const {
@@ -113,11 +113,11 @@
void precache(const SkPaint* paint, const char* text, int numGlyphs);
- void render(const SkPaint* paint, const char *text, uint32_t start, uint32_t len,
+ void render(const SkPaint* paint, const char *text,
int numGlyphs, int x, int y, RenderMode mode, uint8_t *bitmap,
uint32_t bitmapW, uint32_t bitmapH, Rect *bounds, const float* positions);
- void measure(const SkPaint* paint, const char* text, uint32_t start, uint32_t len,
+ void measure(const SkPaint* paint, const char* text,
int numGlyphs, Rect *bounds, const float* positions);
void invalidateTextureCache(CacheTexture* cacheTexture = nullptr);
diff --git a/libs/hwui/unit_tests/OpReordererTests.cpp b/libs/hwui/unit_tests/OpReordererTests.cpp
index ec8048d..d76086c 100644
--- a/libs/hwui/unit_tests/OpReordererTests.cpp
+++ b/libs/hwui/unit_tests/OpReordererTests.cpp
@@ -136,14 +136,14 @@
}
TEST(OpReorderer, simpleBatching) {
- static int SIMPLE_BATCHING_LOOPS = 5;
+ const int LOOPS = 5;
class SimpleBatchingTestRenderer : public TestRendererBase {
public:
void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override {
- EXPECT_TRUE(mIndex++ >= SIMPLE_BATCHING_LOOPS);
+ EXPECT_TRUE(mIndex++ >= LOOPS) << "Bitmaps should be above all rects";
}
void onRectOp(const RectOp& op, const BakedOpState& state) override {
- EXPECT_TRUE(mIndex++ < SIMPLE_BATCHING_LOOPS);
+ EXPECT_TRUE(mIndex++ < LOOPS) << "Rects should be below all bitmaps";
}
};
@@ -153,7 +153,7 @@
// Alternate between drawing rects and bitmaps, with bitmaps overlapping rects.
// Rects don't overlap bitmaps, so bitmaps should be brought to front as a group.
canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
- for (int i = 0; i < SIMPLE_BATCHING_LOOPS; i++) {
+ for (int i = 0; i < LOOPS; i++) {
canvas.translate(0, 10);
canvas.drawRect(0, 0, 10, 10, SkPaint());
canvas.drawBitmap(bitmap, 5, 0, nullptr);
@@ -164,7 +164,35 @@
OpReorderer reorderer(200, 200, *dl, sLightCenter);
SimpleBatchingTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(2 * SIMPLE_BATCHING_LOOPS, renderer.getIndex()); // 2 x loops ops, because no merging (TODO: force no merging)
+ EXPECT_EQ(2 * LOOPS, renderer.getIndex())
+ << "Expect number of ops = 2 * loop count"; // TODO: force no merging
+}
+
+TEST(OpReorderer, textStrikethroughBatching) {
+ const int LOOPS = 5;
+ class TextStrikethroughTestRenderer : public TestRendererBase {
+ public:
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ EXPECT_TRUE(mIndex++ >= LOOPS) << "Strikethrough rects should be above all text";
+ }
+ void onTextOp(const TextOp& op, const BakedOpState& state) override {
+ EXPECT_TRUE(mIndex++ < LOOPS) << "Text should be beneath all strikethrough rects";
+ }
+ };
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 2000, [](RecordingCanvas& canvas) {
+ SkPaint textPaint;
+ textPaint.setAntiAlias(true);
+ textPaint.setTextSize(20);
+ textPaint.setStrikeThruText(true);
+ for (int i = 0; i < LOOPS; i++) {
+ TestUtils::drawTextToCanvas(&canvas, "test text", textPaint, 10, 100 * (i + 1));
+ }
+ });
+ OpReorderer reorderer(200, 2000, *dl, sLightCenter);
+ TextStrikethroughTestRenderer renderer;
+ reorderer.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(2 * LOOPS, renderer.getIndex())
+ << "Expect number of ops = 2 * loop count"; // TODO: force no merging
}
TEST(OpReorderer, renderNode) {
diff --git a/libs/hwui/unit_tests/RecordingCanvasTests.cpp b/libs/hwui/unit_tests/RecordingCanvasTests.cpp
index 22190f5..c23d47e 100644
--- a/libs/hwui/unit_tests/RecordingCanvasTests.cpp
+++ b/libs/hwui/unit_tests/RecordingCanvasTests.cpp
@@ -41,21 +41,110 @@
playbackOps(*dl, [](const RecordedOp& op) { ADD_FAILURE(); });
}
-TEST(RecordingCanvas, testSimpleRectRecord) {
+TEST(RecordingCanvas, drawLines) {
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
+ SkPaint paint;
+ paint.setStrokeWidth(20);
+ float points[] = { 0, 0, 20, 10, 30, 40, 90 }; // NB: only 1 valid line
+ canvas.drawLines(&points[0], 7, paint);
+ });
+
+ ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op";
+ auto op = dl->getOps()[0];
+ ASSERT_EQ(RecordedOpId::LinesOp, op->opId);
+ EXPECT_EQ(4, ((LinesOp*)op)->floatCount)
+ << "float count must be rounded down to closest multiple of 4";
+ EXPECT_EQ(Rect(-10, -10, 30, 20), op->unmappedBounds)
+ << "unmapped bounds must be size of line, outset by 1/2 stroke width";
+}
+
+TEST(RecordingCanvas, drawRect) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
canvas.drawRect(10, 20, 90, 180, SkPaint());
});
+ ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op";
+ auto op = *(dl->getOps()[0]);
+ ASSERT_EQ(RecordedOpId::RectOp, op.opId);
+ EXPECT_EQ(Rect(0, 0, 100, 200), op.localClipRect);
+ EXPECT_EQ(Rect(10, 20, 90, 180), op.unmappedBounds);
+}
+
+TEST(RecordingCanvas, drawText) {
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+ SkPaint paint;
+ paint.setAntiAlias(true);
+ paint.setTextSize(20);
+ TestUtils::drawTextToCanvas(&canvas, "test text", paint, 25, 25);
+ });
+
int count = 0;
playbackOps(*dl, [&count](const RecordedOp& op) {
count++;
- ASSERT_EQ(RecordedOpId::RectOp, op.opId);
- ASSERT_EQ(Rect(0, 0, 100, 200), op.localClipRect);
- ASSERT_EQ(Rect(10, 20, 90, 180), op.unmappedBounds);
+ ASSERT_EQ(RecordedOpId::TextOp, op.opId);
+ EXPECT_EQ(Rect(0, 0, 200, 200), op.localClipRect);
+ EXPECT_TRUE(op.localMatrix.isIdentity());
+ EXPECT_TRUE(op.unmappedBounds.contains(25, 15, 50, 25))
+ << "Op expected to be 25+ pixels wide, 10+ pixels tall";
});
ASSERT_EQ(1, count);
}
+TEST(RecordingCanvas, drawText_strikeThruAndUnderline) {
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+ SkPaint paint;
+ paint.setAntiAlias(true);
+ paint.setTextSize(20);
+ for (int i = 0; i < 2; i++) {
+ for (int j = 0; j < 2; j++) {
+ paint.setUnderlineText(i != 0);
+ paint.setStrikeThruText(j != 0);
+ TestUtils::drawTextToCanvas(&canvas, "test text", paint, 25, 25);
+ }
+ }
+ });
+
+ auto ops = dl->getOps();
+ ASSERT_EQ(8u, ops.size());
+
+ int index = 0;
+ EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId); // no underline or strikethrough
+
+ EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId);
+ EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // strikethrough only
+
+ EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId);
+ EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // underline only
+
+ EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId);
+ EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // underline
+ EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // strikethrough
+}
+
+TEST(RecordingCanvas, drawText_forceAlignLeft) {
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+ SkPaint paint;
+ paint.setAntiAlias(true);
+ paint.setTextSize(20);
+ paint.setTextAlign(SkPaint::kLeft_Align);
+ TestUtils::drawTextToCanvas(&canvas, "test text", paint, 25, 25);
+ paint.setTextAlign(SkPaint::kCenter_Align);
+ TestUtils::drawTextToCanvas(&canvas, "test text", paint, 25, 25);
+ paint.setTextAlign(SkPaint::kRight_Align);
+ TestUtils::drawTextToCanvas(&canvas, "test text", paint, 25, 25);
+ });
+
+ int count = 0;
+ playbackOps(*dl, [&count](const RecordedOp& op) {
+ count++;
+ ASSERT_EQ(RecordedOpId::TextOp, op.opId);
+ EXPECT_EQ(SkPaint::kLeft_Align, op.paint->getTextAlign())
+ << "recorded drawText commands must force kLeft_Align on their paint";
+ EXPECT_EQ(SkPaint::kGlyphID_TextEncoding, op.paint->getTextEncoding()); // verify TestUtils
+ });
+ ASSERT_EQ(3, count);
+}
+
TEST(RecordingCanvas, backgroundAndImage) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
SkBitmap bitmap;
@@ -109,7 +198,7 @@
ASSERT_EQ(2, count);
}
-TEST(RecordingCanvas, saveLayerSimple) {
+TEST(RecordingCanvas, saveLayer_simple) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
canvas.saveLayerAlpha(10, 20, 190, 180, 128, SkCanvas::kARGB_ClipLayer_SaveFlag);
canvas.drawRect(10, 20, 190, 180, SkPaint());
@@ -143,7 +232,7 @@
EXPECT_EQ(3, count);
}
-TEST(RecordingCanvas, saveLayerViewportCrop) {
+TEST(RecordingCanvas, saveLayer_viewportCrop) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
// shouldn't matter, since saveLayer will clip to its bounds
canvas.clipRect(-1000, -1000, 1000, 1000, SkRegion::kReplace_Op);
@@ -167,7 +256,7 @@
EXPECT_EQ(3, count);
}
-TEST(RecordingCanvas, saveLayerRotateUnclipped) {
+TEST(RecordingCanvas, saveLayer_rotateUnclipped) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
canvas.translate(100, 100);
@@ -193,7 +282,7 @@
EXPECT_EQ(3, count);
}
-TEST(RecordingCanvas, saveLayerRotateClipped) {
+TEST(RecordingCanvas, saveLayer_rotateClipped) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
canvas.translate(100, 100);
@@ -224,7 +313,7 @@
EXPECT_EQ(3, count);
}
-TEST(RecordingCanvas, testReorderBarrier) {
+TEST(RecordingCanvas, insertReorderBarrier) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
canvas.drawRect(0, 0, 400, 400, SkPaint());
canvas.insertReorderBarrier(true);
diff --git a/libs/hwui/utils/TestUtils.cpp b/libs/hwui/utils/TestUtils.cpp
index 84230a7..dd6fc36 100644
--- a/libs/hwui/utils/TestUtils.cpp
+++ b/libs/hwui/utils/TestUtils.cpp
@@ -36,5 +36,46 @@
| (int)((startB + (int)(fraction * (endB - startB))));
}
+void TestUtils::drawTextToCanvas(TestCanvas* canvas, const char* text,
+ const SkPaint& inPaint, float x, float y) {
+ // copy to force TextEncoding (which JNI layer would have done)
+ SkPaint paint(inPaint);
+ paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+
+ SkMatrix identity;
+ identity.setIdentity();
+ SkSurfaceProps surfaceProps(0, kUnknown_SkPixelGeometry);
+ SkAutoGlyphCacheNoGamma autoCache(paint, &surfaceProps, &identity);
+
+ float totalAdvance = 0;
+ std::vector<glyph_t> glyphs;
+ std::vector<float> positions;
+ Rect bounds;
+ while (*text != '\0') {
+ SkUnichar unichar = SkUTF8_NextUnichar(&text);
+ glyph_t glyph = autoCache.getCache()->unicharToGlyph(unichar);
+ autoCache.getCache()->unicharToGlyph(unichar);
+
+ // push glyph and its relative position
+ glyphs.push_back(glyph);
+ positions.push_back(totalAdvance);
+ positions.push_back(0);
+
+ // compute bounds
+ SkGlyph skGlyph = autoCache.getCache()->getUnicharMetrics(unichar);
+ Rect glyphBounds(skGlyph.fWidth, skGlyph.fHeight);
+ glyphBounds.translate(totalAdvance + skGlyph.fLeft, skGlyph.fTop);
+ bounds.unionWith(glyphBounds);
+
+ // advance next character
+ SkScalar skWidth;
+ paint.getTextWidths(&glyph, sizeof(glyph), &skWidth, NULL);
+ totalAdvance += skWidth;
+ }
+ bounds.translate(x, y);
+ canvas->drawText(glyphs.data(), positions.data(), glyphs.size(), paint, x, y,
+ bounds.left, bounds.top, bounds.right, bounds.bottom, totalAdvance);
+}
+
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/utils/TestUtils.h b/libs/hwui/utils/TestUtils.h
index f7f4f2d..f9fa242 100644
--- a/libs/hwui/utils/TestUtils.h
+++ b/libs/hwui/utils/TestUtils.h
@@ -196,6 +196,9 @@
static SkColor interpolateColor(float fraction, SkColor start, SkColor end);
+ static void drawTextToCanvas(TestCanvas* canvas, const char* text,
+ const SkPaint& inPaint, float x, float y);
+
private:
static void syncHierarchyPropertiesAndDisplayListImpl(RenderNode* node) {
node->syncProperties();