if (addDrawOp(op)) {
// precache if draw operation is visible
FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(paint);
- fontRenderer.precache(paint, text, count, *mSnapshot->transform);
+ fontRenderer.precache(paint, text, count, mat4::identity());
}
return DrawGlInfo::kStatusDone;
}
if (addDrawOp(op)) {
// precache if draw operation is visible
FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(paint);
- fontRenderer.precache(paint, text, count, *mSnapshot->transform);
+ fontRenderer.precache(paint, text, count, mat4::identity());
}
return DrawGlInfo::kStatusDone;
}
if (addDrawOp(op)) {
// precache if draw operation is visible
FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(paint);
- fontRenderer.precache(paint, text, count, *mSnapshot->transform);
+ const bool pureTranslate = mSnapshot->transform->isPureTranslate();
+ fontRenderer.precache(paint, text, count,
+ pureTranslate ? mat4::identity() : *mSnapshot->transform);
}
return DrawGlInfo::kStatusDone;
}
void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyph,
uint32_t* retOriginX, uint32_t* retOriginY, bool precaching) {
checkInit();
+
+ // If the glyph bitmap is empty let's assum the glyph is valid
+ // so we can avoid doing extra work later on
+ if (glyph.fWidth == 0 || glyph.fHeight == 0) {
+ cachedGlyph->mIsValid = true;
+ cachedGlyph->mCacheTexture = NULL;
+ return;
+ }
+
cachedGlyph->mIsValid = false;
+
// If the glyph is too tall, don't cache it
if (glyph.fHeight + TEXTURE_BORDER_SIZE * 2 >
mCacheTextures[mCacheTextures.size() - 1]->getHeight()) {
alpha *= mSnapshot->alpha;
- mCaches.activeTexture(0);
- Texture* texture = mCaches.textureCache.get(bitmap);
- if (!texture) return DrawGlInfo::kStatusDone;
- const AutoTexture autoCleanup(texture);
- texture->setWrap(GL_CLAMP_TO_EDGE, true);
- texture->setFilter(GL_LINEAR, true);
-
const Patch* mesh = mCaches.patchCache.get(bitmap->width(), bitmap->height(),
right - left, bottom - top, xDivs, yDivs, colors, width, height, numColors);
if (CC_LIKELY(mesh && mesh->verticesCount > 0)) {
+ mCaches.activeTexture(0);
+ Texture* texture = mCaches.textureCache.get(bitmap);
+ if (!texture) return DrawGlInfo::kStatusDone;
+ const AutoTexture autoCleanup(texture);
+ texture->setWrap(GL_CLAMP_TO_EDGE, true);
+ texture->setFilter(GL_LINEAR, true);
+
const bool pureTranslate = mSnapshot->transform->isPureTranslate();
// Mark the current layer dirty where we are going to draw the patch
if (hasLayer() && mesh->hasEmptyQuads) {
const float oldX = x;
const float oldY = y;
const bool pureTranslate = mSnapshot->transform->isPureTranslate();
+ const bool isPerspective = mSnapshot->transform->isPerspective();
if (CC_LIKELY(pureTranslate)) {
x = (int) floorf(x + mSnapshot->transform->getTranslateX() + 0.5f);
fontRenderer.setFont(paint, pureTranslate ? mat4::identity() : *mSnapshot->transform);
// Pick the appropriate texture filtering
- bool linearFilter = !mSnapshot->transform->isPureTranslate() ||
- fabs(y - (int) y) > 0.0f || fabs(x - (int) x) > 0.0f;
+ bool linearFilter = !pureTranslate || fabs(y - (int) y) > 0.0f || fabs(x - (int) x) > 0.0f;
// The font renderer will always use texture unit 0
mCaches.activeTexture(0);
setupDrawShader();
setupDrawBlending(true, mode);
setupDrawProgram();
- setupDrawModelView(x, y, x, y, true, true);
+ setupDrawModelView(x, y, x, y, !isPerspective, true);
// See comment above; the font renderer must use texture unit 0
// assert(mTextureUnit == 0)
setupDrawTexture(fontRenderer.getTexture(linearFilter));
setupDrawPureColorUniforms();
setupDrawColorFilterUniforms();
- setupDrawShaderUniforms(true);
+ setupDrawShaderUniforms(!isPerspective);
setupDrawTextGammaUniforms();
const Rect* clip = mSnapshot->hasPerspectiveTransform() ? NULL : mSnapshot->clipRect;
}
if (status && hasActiveLayer) {
+ if (isPerspective) {
+ mSnapshot->transform->mapRect(bounds);
+ }
dirtyLayerUnchecked(bounds, getRegion());
}
mStyle = paint->getStyle();
mStrokeWidth = paint->getStrokeWidth();
mAntiAliasing = paint->isAntiAlias();
+ mLookupTransform.reset();
mLookupTransform[SkMatrix::kMScaleX] = matrix.data[mat4::kScaleX];
mLookupTransform[SkMatrix::kMScaleY] = matrix.data[mat4::kScaleY];
mLookupTransform[SkMatrix::kMSkewX] = matrix.data[mat4::kSkewX];
void Font::drawCachedGlyph(CachedGlyphInfo* glyph, int x, int y,
uint8_t* bitmap, uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* pos) {
float nPenX = x + glyph->mBitmapLeft;
- float nPenY = y + (glyph->mBitmapTop + glyph->mBitmapHeight);
+ float nPenY = y + glyph->mBitmapTop + glyph->mBitmapHeight;
float width = (float) glyph->mBitmapWidth;
float height = (float) glyph->mBitmapHeight;
nPenX, nPenY - height, u1, v1, glyph->mCacheTexture);
}
+void Font::drawCachedGlyphPerspective(CachedGlyphInfo* glyph, int x, int y,
+ uint8_t* bitmap, uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* pos) {
+ SkMatrix i;
+ if (!mDescription.mLookupTransform.invert(&i)) {
+ return;
+ }
+
+ SkPoint p[4];
+ p[0].set(glyph->mBitmapLeft, glyph->mBitmapTop + glyph->mBitmapHeight);
+ p[1].set(glyph->mBitmapLeft + glyph->mBitmapWidth, glyph->mBitmapTop + glyph->mBitmapHeight);
+ p[2].set(glyph->mBitmapLeft + glyph->mBitmapWidth, glyph->mBitmapTop);
+ p[3].set(glyph->mBitmapLeft, glyph->mBitmapTop);
+
+ i.mapPoints(p, 4);
+
+ p[0].offset(x, y);
+ p[1].offset(x, y);
+ p[2].offset(x, y);
+ p[3].offset(x, y);
+
+ float u1 = glyph->mBitmapMinU;
+ float u2 = glyph->mBitmapMaxU;
+ float v1 = glyph->mBitmapMinV;
+ float v2 = glyph->mBitmapMaxV;
+
+ mState->appendRotatedMeshQuad(
+ p[0].fX, p[0].fY, u1, v2,
+ p[1].fX, p[1].fY, u2, v2,
+ p[2].fX, p[2].fY, u2, v1,
+ p[3].fX, p[3].fY, u1, v1, glyph->mCacheTexture);
+}
+
void Font::drawCachedGlyphBitmap(CachedGlyphInfo* glyph, int x, int y,
uint8_t* bitmap, uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* pos) {
int nPenX = x + glyph->mBitmapLeft;
penX += SkFixedToFloat(AUTO_KERN(prevRsbDelta, cachedGlyph->mLsbDelta));
prevRsbDelta = cachedGlyph->mRsbDelta;
- if (cachedGlyph->mIsValid) {
+ if (cachedGlyph->mIsValid && cachedGlyph->mCacheTexture) {
drawCachedGlyph(cachedGlyph, penX, hOffset, vOffset, measure, &position, &tangent);
}
}
void Font::precache(SkPaint* paint, const char* text, int numGlyphs) {
-
if (numGlyphs == 0 || text == NULL) {
return;
}
static RenderGlyph gRenderGlyph[] = {
&android::uirenderer::Font::drawCachedGlyph,
+ &android::uirenderer::Font::drawCachedGlyphPerspective,
+ &android::uirenderer::Font::drawCachedGlyphBitmap,
&android::uirenderer::Font::drawCachedGlyphBitmap,
+ &android::uirenderer::Font::measureCachedGlyph,
&android::uirenderer::Font::measureCachedGlyph
};
- RenderGlyph render = gRenderGlyph[mode];
+ RenderGlyph render = gRenderGlyph[(mode << 1) + mTransform.isPerspective()];
text += start;
int glyphsCount = 0;
+ const bool applyTransform = !mTransform.isIdentity() && !mTransform.isPerspective();
const SkPaint::Align align = paint->getTextAlign();
while (glyphsCount < numGlyphs) {
CachedGlyphInfo* cachedGlyph = getCachedGlyph(paint, glyph);
- // If it's still not valid, we couldn't cache it, so we shouldn't draw garbage
- if (cachedGlyph->mIsValid) {
+ // If it's still not valid, we couldn't cache it, so we shouldn't
+ // draw garbage; also skip empty glyphs (spaces)
+ if (cachedGlyph->mIsValid && cachedGlyph->mCacheTexture) {
float penX = x + positions[(glyphsCount << 1)];
float penY = y + positions[(glyphsCount << 1) + 1];
- if (!mTransform.isIdentity()) {
+ if (applyTransform) {
mTransform.mapPoint(penX, penY);
}
glyph->mBitmapWidth = skiaGlyph.fWidth;
glyph->mBitmapHeight = skiaGlyph.fHeight;
- uint32_t cacheWidth = glyph->mCacheTexture->getWidth();
- uint32_t cacheHeight = glyph->mCacheTexture->getHeight();
+ bool empty = skiaGlyph.fWidth == 0 || skiaGlyph.fHeight == 0;
+ if (!empty) {
+ uint32_t cacheWidth = glyph->mCacheTexture->getWidth();
+ uint32_t cacheHeight = glyph->mCacheTexture->getHeight();
- glyph->mBitmapMinU = startX / (float) cacheWidth;
- glyph->mBitmapMinV = startY / (float) cacheHeight;
- glyph->mBitmapMaxU = endX / (float) cacheWidth;
- glyph->mBitmapMaxV = endY / (float) cacheHeight;
+ glyph->mBitmapMinU = startX / (float) cacheWidth;
+ glyph->mBitmapMinV = startY / (float) cacheHeight;
+ glyph->mBitmapMaxU = endX / (float) cacheWidth;
+ glyph->mBitmapMaxV = endY / (float) cacheHeight;
- mState->setTextureDirty();
+ mState->setTextureDirty();
+ }
}
CachedGlyphInfo* Font::cacheGlyph(SkPaint* paint, glyph_t glyph, bool precaching) {
mCachedGlyphs.add(glyph, newGlyph);
const SkGlyph& skiaGlyph = GET_METRICS(paint, glyph, &mDescription.mLookupTransform);
- newGlyph->mGlyphIndex = skiaGlyph.fID;
newGlyph->mIsValid = false;
+ newGlyph->mGlyphIndex = skiaGlyph.fID;
updateGlyphCache(paint, skiaGlyph, newGlyph, precaching);
FontDescription description(paint, matrix);
Font* font = state->mActiveFonts.get(description);
- if (font) {
- font->mTransform.load(matrix);
- return font;
+ if (!font) {
+ font = new Font(state, description);
+ state->mActiveFonts.put(description, font);
}
+ font->mTransform.load(matrix);
- Font* newFont = new Font(state, description);
- state->mActiveFonts.put(description, newFont);
- return newFont;
+ return font;
}
}; // namespace uirenderer
void drawCachedGlyph(CachedGlyphInfo* glyph, int x, int y,
uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH,
Rect* bounds, const float* pos);
+ void drawCachedGlyphPerspective(CachedGlyphInfo* glyph, int x, int y,
+ uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH,
+ Rect* bounds, const float* pos);
void drawCachedGlyphBitmap(CachedGlyphInfo* glyph, int x, int y,
uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH,
Rect* bounds, const float* pos);
</activity>
<activity
+ android:name="Rotate3dTextActivity"
+ android:label="Text/3D Rotation"
+ android:theme="@android:style/Theme.Holo.Light">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="com.android.test.hwui.TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity
android:name="NoAATextActivity"
android:label="Text/Aliased">
<intent-filter>
--- /dev/null
+/*
+ * Copyright (C) 2010 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.
+ */
+
+package com.android.test.hwui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.os.Bundle;
+import android.view.View;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class Rotate3dTextActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ final Rotate3dTextView view = new Rotate3dTextView(this);
+ setContentView(view);
+ }
+
+ public static class Rotate3dTextView extends View {
+ private static final String TEXT = "Hello libhwui! ";
+
+ private final Paint mPaint;
+
+ public Rotate3dTextView(Context c) {
+ super(c);
+
+ mPaint = new Paint();
+ mPaint.setAntiAlias(true);
+ mPaint.setTextSize(50.0f);
+ mPaint.setTextAlign(Paint.Align.CENTER);
+
+ setRotationY(45.0f);
+ setScaleX(2.0f);
+ setScaleY(2.0f);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ canvas.drawText(TEXT, getWidth() / 2.0f, getHeight() / 2.0f, mPaint);
+
+ invalidate();
+ }
+ }
+}