Initial text support in new reorderer/renderer
Removes obsolete drawPosText codepath, and unifies text decoration behavior. Change-Id: I9c563249ab688a3394445a0e7fe1b9d0661f6f7c
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -37,6 +37,7 @@ hwui_src_files := \
|
||||
AnimatorManager.cpp \
|
||||
AssetAtlas.cpp \
|
||||
Caches.cpp \
|
||||
Canvas.cpp \
|
||||
CanvasState.cpp \
|
||||
ClipArea.cpp \
|
||||
DamageAccumulator.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 @@ 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
56
libs/hwui/Canvas.cpp
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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!");
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
/**
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user