Initial text support in new reorderer/renderer

Removes obsolete drawPosText codepath, and unifies text decoration behavior.

Change-Id: I9c563249ab688a3394445a0e7fe1b9d0661f6f7c
This commit is contained in:
Chris Craik
2015-11-19 13:02:43 -08:00
parent ee35d738f4
commit a1717271ca
26 changed files with 544 additions and 317 deletions

View File

@@ -544,39 +544,6 @@ private:
float totalAdvance;
};
// 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 drawTextDecorations(Canvas* canvas, float x, float y, float length, const SkPaint& paint) {
uint32_t flags;
SkDrawFilter* drawFilter = canvas->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)) {
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;
canvas->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;
canvas->drawRect(left, top, right, bottom, paint);
}
}
}
void drawText(Canvas* canvas, const uint16_t* text, int start, int count, int contextCount,
float x, float y, int bidiFlags, const Paint& origPaint, TypefaceImpl* typeface) {
// minikin may modify the original paint
@@ -586,8 +553,8 @@ void drawText(Canvas* canvas, const uint16_t* text, int start, int count, int co
MinikinUtils::doLayout(&layout, &paint, bidiFlags, typeface, text, start, count, contextCount);
size_t nGlyphs = layout.nGlyphs();
uint16_t* glyphs = new uint16_t[nGlyphs];
float* pos = new float[nGlyphs * 2];
std::unique_ptr<uint16_t[]> glyphs(new uint16_t[nGlyphs]);
std::unique_ptr<float[]> pos(new float[nGlyphs * 2]);
x += MinikinUtils::xOffsetForTextAlign(&paint, layout);
@@ -597,13 +564,9 @@ void drawText(Canvas* canvas, const uint16_t* text, int start, int count, int co
bounds.offset(x, y);
}
DrawTextFunctor f(layout, canvas, glyphs, pos, paint, x, y, bounds, layout.getAdvance());
DrawTextFunctor f(layout, canvas, glyphs.get(), pos.get(),
paint, x, y, bounds, layout.getAdvance());
MinikinUtils::forFontRun(layout, &paint, f);
drawTextDecorations(canvas, x, y, layout.getAdvance(), paint);
delete[] glyphs;
delete[] pos;
}
static void drawTextChars(JNIEnv* env, jobject, jlong canvasHandle, jcharArray text,

View File

@@ -37,6 +37,7 @@ hwui_src_files := \
AnimatorManager.cpp \
AssetAtlas.cpp \
Caches.cpp \
Canvas.cpp \
CanvasState.cpp \
ClipArea.cpp \
DamageAccumulator.cpp \

View File

@@ -24,6 +24,9 @@
#include "utils/GLUtils.h"
#include "VertexBuffer.h"
#include <algorithm>
#include <math.h>
namespace android {
namespace uirenderer {
@@ -183,6 +186,10 @@ void BakedOpDispatcher::onBitmapOp(BakedOpRenderer& renderer, const BitmapOp& op
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 @@ void BakedOpDispatcher::onSimpleRectsOp(BakedOpRenderer& renderer, const SimpleR
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;

56
libs/hwui/Canvas.cpp Normal file
View File

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

View File

@@ -149,16 +149,12 @@ public:
// 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 @@ public:
* 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

View File

@@ -423,18 +423,6 @@ void DisplayListCanvas::drawTextOnPath(const uint16_t* glyphs, int count,
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 @@ void DisplayListCanvas::drawText(const uint16_t* glyphs, const float* positions,
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) {

View File

@@ -212,8 +212,6 @@ public:
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; }

View File

@@ -1278,24 +1278,6 @@ private:
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,

View File

@@ -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 @@ void TextDrawFunctor::draw(CacheTexture& texture, bool linearFiltering) {
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 @@ void FontRenderer::setFont(const SkPaint* paint, const SkMatrix& matrix) {
}
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 @@ FontRenderer::DropShadow FontRenderer::renderDropShadow(const SkPaint* paint, co
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 @@ FontRenderer::DropShadow FontRenderer::renderDropShadow(const SkPaint* paint, co
// 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 @@ void FontRenderer::endPrecaching() {
}
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::renderPosText(const SkPaint* paint, const Rect* clip, const c
}
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;

View File

@@ -44,13 +44,28 @@ namespace RSC {
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 @@ public:
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 @@ public:
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 @@ public:
// 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;

View File

@@ -671,6 +671,13 @@ void OpReorderer::onBitmapOp(const BitmapOp& op) {
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 @@ void OpReorderer::onSimpleRectsOp(const SimpleRectsOp& op) {
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,

View File

@@ -1950,7 +1950,7 @@ void OpenGLRenderer::drawRect(float left, float top, float right, float bottom,
}
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 @@ void OpenGLRenderer::drawTextShadow(const SkPaint* paint, const char* text,
// 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 @@ bool OpenGLRenderer::canSkipText(const SkPaint* paint) const {
&& 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 @@ void OpenGLRenderer::drawText(const char* text, int bytesCount, int count, float
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 @@ void OpenGLRenderer::drawText(const char* text, int bytesCount, int count, float
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 @@ void OpenGLRenderer::drawText(const char* text, int bytesCount, int count, float
dirtyLayerUnchecked(layerBounds, getRegion());
}
drawTextDecorations(totalAdvance, oldX, oldY, paint);
mDirty = true;
}
@@ -2236,12 +2188,17 @@ void OpenGLRenderer::drawTextOnPath(const char* text, int bytesCount, int count,
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 @@ void OpenGLRenderer::drawPathTexture(PathTexture* texture, float x, float y,
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;

View File

@@ -193,8 +193,6 @@ public:
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 @@ private:
*/
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 @@ private:
* @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);

View File

@@ -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 @@ struct Vertex;
*/
#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 @@ struct RenderNodeOp : RecordedOp {
bool skipInOrderDraw = false;
};
////////////////////////////////////////////////////////////////////////////////////////////////////
// Standard Ops
////////////////////////////////////////////////////////////////////////////////////////////////////
struct BitmapOp : RecordedOp {
BitmapOp(BASE_PARAMS, const SkBitmap* bitmap)
: SUPER(BitmapOp)
@@ -106,6 +113,15 @@ struct BitmapOp : RecordedOp {
// 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 @@ struct SimpleRectsOp : RecordedOp { // Filled, no AA (TODO: better name?)
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.

View File

@@ -230,12 +230,9 @@ void RecordingCanvas::drawColor(int color, SkXfermode::Mode mode) {
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::drawPaint(const SkPaint& paint) {
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 @@ void RecordingCanvas::drawNinePatch(const SkBitmap& bitmap, const android::Res_p
}
// 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!");
}
void RecordingCanvas::drawPosText(const uint16_t* text, const float* positions, int count,
int posCount, const SkPaint& paint) {
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::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!");
}

View File

@@ -178,8 +178,6 @@ public:
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 @@ private:
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 @@ private:
// 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());

View File

@@ -260,13 +260,6 @@ public:
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);
}

View File

@@ -131,8 +131,6 @@ public:
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 @@ private:
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 @@ void SkiaCanvas::drawText(const uint16_t* text, const float* positions, int coun
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,

View File

@@ -30,8 +30,7 @@ namespace uirenderer {
///////////////////////////////////////////////////////////////////////////////
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_t ShadowText::hash() const {
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 @@ hash_t ShadowText::hash() const {
}
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 @@ int ShadowText::compare(const ShadowText& lhs, const ShadowText& rhs) {
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 @@ int ShadowText::compare(const ShadowText& lhs, const ShadowText& rhs) {
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 @@ void TextDropShadowCache::clear() {
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;

View File

@@ -34,14 +34,14 @@ class Caches;
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 @@ struct ShadowText {
}
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 @@ public:
*/
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);
/**

View File

@@ -291,20 +291,18 @@ CachedGlyphInfo* Font::getCachedGlyph(const SkPaint* paint, glyph_t textUnit, bo
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 @@ void Font::render(const SkPaint* paint, const char *text, uint32_t start, uint32
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::render(const SkPaint* paint, const char *text, uint32_t start, uint32
}
}
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::precache(const SkPaint* paint, const char* text, int numGlyphs) {
}
}
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 @@ void Font::render(const SkPaint* paint, const char* text, uint32_t start, uint32
};
RenderGlyph render = gRenderGlyph[(mode << 1) + !mIdentityTransform];
text += start;
int glyphsCount = 0;
while (glyphsCount < numGlyphs) {

View File

@@ -82,10 +82,10 @@ public:
~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 @@ private:
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);

View File

@@ -136,14 +136,14 @@ TEST(OpReorderer, simpleRejection) {
}
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 @@ TEST(OpReorderer, simpleBatching) {
// 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 @@ TEST(OpReorderer, simpleBatching) {
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) {

View File

@@ -41,21 +41,110 @@ TEST(RecordingCanvas, emptyPlayback) {
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 @@ TEST(RecordingCanvas, backgroundAndImage) {
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 @@ TEST(RecordingCanvas, saveLayerSimple) {
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 @@ TEST(RecordingCanvas, saveLayerViewportCrop) {
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 @@ TEST(RecordingCanvas, saveLayerRotateUnclipped) {
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 @@ TEST(RecordingCanvas, saveLayerRotateClipped) {
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);

View File

@@ -36,5 +36,46 @@ SkColor TestUtils::interpolateColor(float fraction, SkColor start, SkColor end)
| (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 */

View File

@@ -196,6 +196,9 @@ public:
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();