Add text rendering.
Change-Id: Ibe5a9fa844d531b31b55e43de403a98d49f659b9
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
index 1424638..78648ff 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/GLES20Canvas.java
@@ -410,9 +410,21 @@
public void drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) {
// Shaders are ignored when drawing bitmaps
final int nativePaint = paint == null ? 0 : paint.mNativePaint;
- nDrawBitmap(mRenderer, bitmap.mNativeBitmap, src.left, src.top, src.right, src.bottom,
- dst.left, dst.top, dst.right, dst.bottom, nativePaint
- );
+
+ int left, top, right, bottom;
+ if (src == null) {
+ left = top = 0;
+ right = bitmap.getWidth();
+ bottom = bitmap.getHeight();
+ } else {
+ left = src.left;
+ right = src.right;
+ top = src.top;
+ bottom = src.bottom;
+ }
+
+ nDrawBitmap(mRenderer, bitmap.mNativeBitmap, left, top, right, bottom,
+ dst.left, dst.top, dst.right, dst.bottom, nativePaint);
}
@Override
@@ -420,8 +432,7 @@
// Shaders are ignored when drawing bitmaps
final int nativePaint = paint == null ? 0 : paint.mNativePaint;
nDrawBitmap(mRenderer, bitmap.mNativeBitmap, src.left, src.top, src.right, src.bottom,
- dst.left, dst.top, dst.right, dst.bottom, nativePaint
- );
+ dst.left, dst.top, dst.right, dst.bottom, nativePaint);
}
private native void nDrawBitmap(int renderer, int bitmap,
diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp
index fa4d23c..2f1dcb6 100644
--- a/core/jni/android_view_GLES20Canvas.cpp
+++ b/core/jni/android_view_GLES20Canvas.cpp
@@ -255,7 +255,8 @@
OpenGLRenderer* renderer, jcharArray text, int index, int count,
jfloat x, jfloat y, int flags, SkPaint* paint) {
jchar* textArray = env->GetCharArrayElements(text, NULL);
- // TODO: draw from textArray + index
+ // TODO: Prepare the text for RTL
+ renderer->drawText((const char*) (textArray + index), count, x, y, paint);
env->ReleaseCharArrayElements(text, textArray, JNI_ABORT);
}
@@ -263,7 +264,8 @@
OpenGLRenderer* renderer, jstring text, int start, int end,
jfloat x, jfloat y, int flags, SkPaint* paint) {
const jchar* textArray = env->GetStringChars(text, NULL);
- // TODO: draw from textArray + start
+ // TODO: Prepare the text for RTL
+ renderer->drawText((const char*) (textArray + start), end - start, x, y, paint);
env->ReleaseStringChars(text, textArray);
}
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 3d63aa6..6349cb3 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -931,15 +931,14 @@
}
/**
- * Temporary API to expose layer drawing. This draws a shadow layer below
- * the main layer, with the specified offset and color, and blur radius.
- * If radius is 0, then the shadow layer is removed.
+ * This draws a shadow layer below the main layer, with the specified
+ * offset and color, and blur radius. If radius is 0, then the shadow
+ * layer is removed.
*/
- public native void setShadowLayer(float radius, float dx, float dy,
- int color);
+ public native void setShadowLayer(float radius, float dx, float dy, int color);
/**
- * Temporary API to clear the shadow layer.
+ * Clear the shadow layer.
*/
public void clearShadowLayer() {
setShadowLayer(0, 0, 0, 0);
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 1bdb9d3..172952a 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -2,6 +2,7 @@
include $(CLEAR_VARS)
LOCAL_SRC_FILES:= \
+ FontRenderer.cpp \
GradientCache.cpp \
LayerCache.cpp \
Matrix.cpp \
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
new file mode 100644
index 0000000..8557b87
--- /dev/null
+++ b/libs/hwui/FontRenderer.cpp
@@ -0,0 +1,467 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "OpenGLRenderer"
+
+#include "FontRenderer.h"
+
+#include <SkUtils.h>
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Font
+///////////////////////////////////////////////////////////////////////////////
+
+Font::Font(FontRenderer* state, uint32_t fontId, float fontSize) :
+ mState(state), mFontId(fontId), mFontSize(fontSize) {
+}
+
+
+Font::~Font() {
+ for (uint32_t ct = 0; ct < mState->mActiveFonts.size(); ct++) {
+ if (mState->mActiveFonts[ct] == this) {
+ mState->mActiveFonts.removeAt(ct);
+ break;
+ }
+ }
+
+ for (uint32_t i = 0; i < mCachedGlyphs.size(); i++) {
+ CachedGlyphInfo *glyph = mCachedGlyphs.valueAt(i);
+ delete glyph;
+ }
+}
+
+void Font::invalidateTextureCache() {
+ for (uint32_t i = 0; i < mCachedGlyphs.size(); i++) {
+ mCachedGlyphs.valueAt(i)->mIsValid = false;
+ }
+}
+
+void Font::drawCachedGlyph(CachedGlyphInfo* glyph, int x, int y) {
+ FontRenderer *state = mState;
+
+ int nPenX = x + glyph->mBitmapLeft;
+ int nPenY = y + glyph->mBitmapTop + glyph->mBitmapHeight;
+
+ state->appendMeshQuad(nPenX, nPenY, 0, glyph->mBitmapMinU, glyph->mBitmapMaxV,
+ nPenX + (int) glyph->mBitmapWidth, nPenY, 0, glyph->mBitmapMaxU, glyph->mBitmapMaxV,
+ nPenX + (int) glyph->mBitmapWidth, nPenY - (int) glyph->mBitmapHeight,
+ 0, glyph->mBitmapMaxU, glyph->mBitmapMinV, nPenX, nPenY - (int) glyph->mBitmapHeight,
+ 0, glyph->mBitmapMinU, glyph->mBitmapMinV);
+}
+
+void Font::renderUTF(SkPaint* paint, const char *text, uint32_t len, uint32_t start, int numGlyphs,
+ int x, int y) {
+ if (numGlyphs == 0 || text == NULL || len == 0) {
+ return;
+ }
+
+ int penX = x, penY = y;
+ int glyphsLeft = 1;
+ if (numGlyphs > 0) {
+ glyphsLeft = numGlyphs;
+ }
+
+ //size_t index = start;
+ //size_t nextIndex = 0;
+
+ text += start;
+
+ while (glyphsLeft > 0) {
+ //int32_t utfChar = utf32_at(text, len, index, &nextIndex);
+ int32_t utfChar = SkUTF16_NextUnichar((const uint16_t**) &text);
+
+ // Reached the end of the string or encountered
+ if (utfChar < 0) {
+ break;
+ }
+
+ // Move to the next character in the array
+ //index = nextIndex;
+
+ CachedGlyphInfo *cachedGlyph = mCachedGlyphs.valueFor(utfChar);
+
+ if (cachedGlyph == NULL) {
+ cachedGlyph = cacheGlyph(paint, utfChar);
+ }
+ // Is the glyph still in texture cache?
+ if (!cachedGlyph->mIsValid) {
+ const SkGlyph& skiaGlyph = paint->getUnicharMetrics(utfChar);
+ updateGlyphCache(paint, skiaGlyph, cachedGlyph);
+ }
+
+ // If it's still not valid, we couldn't cache it, so we shouldn't draw garbage
+ if (cachedGlyph->mIsValid) {
+ drawCachedGlyph(cachedGlyph, penX, penY);
+ }
+
+ // TODO: Check how to do this conversion
+ penX += SkFixedRound(cachedGlyph->mAdvanceX);
+
+ // If we were given a specific number of glyphs, decrement
+ if (numGlyphs > 0) {
+ glyphsLeft--;
+ }
+ }
+}
+
+void Font::updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, CachedGlyphInfo *glyph) {
+ glyph->mAdvanceX = skiaGlyph.fAdvanceX;
+ glyph->mAdvanceY = skiaGlyph.fAdvanceY;
+ glyph->mBitmapLeft = skiaGlyph.fLeft;
+ glyph->mBitmapTop = skiaGlyph.fTop;
+
+ uint32_t startX = 0;
+ uint32_t startY = 0;
+
+ // Let the font state figure out where to put the bitmap
+ FontRenderer *state = mState;
+ // Get the bitmap for the glyph
+ paint->findImage(skiaGlyph);
+ glyph->mIsValid = state->cacheBitmap(skiaGlyph, &startX, &startY);
+
+ if (!glyph->mIsValid) {
+ return;
+ }
+
+ uint32_t endX = startX + skiaGlyph.fWidth;
+ uint32_t endY = startY + skiaGlyph.fHeight;
+
+ glyph->mBitmapWidth = skiaGlyph.fWidth;
+ glyph->mBitmapHeight = skiaGlyph.fHeight;
+
+ uint32_t cacheWidth = state->getCacheWidth();
+ uint32_t cacheHeight = state->getCacheHeight();
+
+ glyph->mBitmapMinU = (float) startX / (float) cacheWidth;
+ glyph->mBitmapMinV = (float) startY / (float) cacheHeight;
+ glyph->mBitmapMaxU = (float) endX / (float) cacheWidth;
+ glyph->mBitmapMaxV = (float) endY / (float) cacheHeight;
+
+ state->mUploadTexture = true;
+}
+
+Font::CachedGlyphInfo *Font::cacheGlyph(SkPaint* paint, int32_t glyph) {
+ CachedGlyphInfo *newGlyph = new CachedGlyphInfo();
+ mCachedGlyphs.add(glyph, newGlyph);
+
+ const SkGlyph& skiaGlyph = paint->getUnicharMetrics(glyph);
+ newGlyph->mGlyphIndex = skiaGlyph.fID;
+ newGlyph->mIsValid = false;
+
+ updateGlyphCache(paint, skiaGlyph, newGlyph);
+
+ return newGlyph;
+}
+
+Font* Font::create(FontRenderer* state, uint32_t fontId, float fontSize) {
+ Vector<Font*> &activeFonts = state->mActiveFonts;
+
+ for (uint32_t i = 0; i < activeFonts.size(); i++) {
+ Font *ithFont = activeFonts[i];
+ if (ithFont->mFontId == fontId && ithFont->mFontSize == fontSize) {
+ return ithFont;
+ }
+ }
+
+ Font* newFont = new Font(state, fontId, fontSize);
+ activeFonts.push(newFont);
+ return newFont;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// FontRenderer
+///////////////////////////////////////////////////////////////////////////////
+
+FontRenderer::FontRenderer() {
+ mInitialized = false;
+ mMaxNumberOfQuads = 1024;
+ mCurrentQuadIndex = 0;
+
+ mIndexBufferID = 0;
+
+ mCacheWidth = 1024;
+ mCacheHeight = 256;
+}
+
+FontRenderer::~FontRenderer() {
+ for (uint32_t i = 0; i < mCacheLines.size(); i++) {
+ delete mCacheLines[i];
+ }
+ mCacheLines.clear();
+
+ delete mTextTexture;
+
+ Vector<Font*> fontsToDereference = mActiveFonts;
+ for (uint32_t i = 0; i < fontsToDereference.size(); i++) {
+ delete fontsToDereference[i];
+ }
+}
+
+void FontRenderer::flushAllAndInvalidate() {
+ if (mCurrentQuadIndex != 0) {
+ issueDrawCommand();
+ mCurrentQuadIndex = 0;
+ }
+ for (uint32_t i = 0; i < mActiveFonts.size(); i++) {
+ mActiveFonts[i]->invalidateTextureCache();
+ }
+ for (uint32_t i = 0; i < mCacheLines.size(); i++) {
+ mCacheLines[i]->mCurrentCol = 0;
+ }
+}
+
+bool FontRenderer::cacheBitmap(const SkGlyph& glyph, uint32_t *retOriginX, uint32_t *retOriginY) {
+ // If the glyph is too tall, don't cache it
+ if (glyph.fWidth > mCacheLines[mCacheLines.size() - 1]->mMaxHeight) {
+ LOGE("Font size to large to fit in cache. width, height = %i, %i",
+ (int) glyph.fWidth, (int) glyph.fHeight);
+ return false;
+ }
+
+ // Now copy the bitmap into the cache texture
+ uint32_t startX = 0;
+ uint32_t startY = 0;
+
+ bool bitmapFit = false;
+ for (uint32_t i = 0; i < mCacheLines.size(); i++) {
+ bitmapFit = mCacheLines[i]->fitBitmap(glyph, &startX, &startY);
+ if (bitmapFit) {
+ break;
+ }
+ }
+
+ // If the new glyph didn't fit, flush the state so far and invalidate everything
+ if (!bitmapFit) {
+ flushAllAndInvalidate();
+
+ // Try to fit it again
+ for (uint32_t i = 0; i < mCacheLines.size(); i++) {
+ bitmapFit = mCacheLines[i]->fitBitmap(glyph, &startX, &startY);
+ if (bitmapFit) {
+ break;
+ }
+ }
+
+ // if we still don't fit, something is wrong and we shouldn't draw
+ if (!bitmapFit) {
+ LOGE("Bitmap doesn't fit in cache. width, height = %i, %i",
+ (int) glyph.fWidth, (int) glyph.fHeight);
+ return false;
+ }
+ }
+
+ *retOriginX = startX;
+ *retOriginY = startY;
+
+ uint32_t endX = startX + glyph.fWidth;
+ uint32_t endY = startY + glyph.fHeight;
+
+ uint32_t cacheWidth = mCacheWidth;
+
+ unsigned char *cacheBuffer = mTextTexture;
+ unsigned char *bitmapBuffer = (unsigned char*) glyph.fImage;
+ unsigned int stride = glyph.rowBytes();
+
+ uint32_t cacheX = 0, bX = 0, cacheY = 0, bY = 0;
+ for (cacheX = startX, bX = 0; cacheX < endX; cacheX++, bX++) {
+ for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY++) {
+ unsigned char tempCol = bitmapBuffer[bY * stride + bX];
+ cacheBuffer[cacheY * cacheWidth + cacheX] = tempCol;
+ }
+ }
+
+ return true;
+}
+
+void FontRenderer::initTextTexture() {
+ mTextTexture = new unsigned char[mCacheWidth * mCacheHeight];
+ mUploadTexture = false;
+
+ glGenTextures(1, &mTextureId);
+ glBindTexture(GL_TEXTURE_2D, mTextureId);
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+ // Split up our cache texture into lines of certain widths
+ int nextLine = 0;
+ mCacheLines.push(new CacheTextureLine(16, mCacheWidth, nextLine, 0));
+ nextLine += mCacheLines.top()->mMaxHeight;
+ mCacheLines.push(new CacheTextureLine(24, mCacheWidth, nextLine, 0));
+ nextLine += mCacheLines.top()->mMaxHeight;
+ mCacheLines.push(new CacheTextureLine(32, mCacheWidth, nextLine, 0));
+ nextLine += mCacheLines.top()->mMaxHeight;
+ mCacheLines.push(new CacheTextureLine(32, mCacheWidth, nextLine, 0));
+ nextLine += mCacheLines.top()->mMaxHeight;
+ mCacheLines.push(new CacheTextureLine(40, mCacheWidth, nextLine, 0));
+ nextLine += mCacheLines.top()->mMaxHeight;
+ mCacheLines.push(new CacheTextureLine(mCacheHeight - nextLine, mCacheWidth, nextLine, 0));
+}
+
+// Avoid having to reallocate memory and render quad by quad
+void FontRenderer::initVertexArrayBuffers() {
+ uint32_t numIndicies = mMaxNumberOfQuads * 6;
+ uint32_t indexBufferSizeBytes = numIndicies * sizeof(uint16_t);
+ uint16_t *indexBufferData = (uint16_t*) malloc(indexBufferSizeBytes);
+
+ // Four verts, two triangles , six indices per quad
+ for (uint32_t i = 0; i < mMaxNumberOfQuads; i++) {
+ int i6 = i * 6;
+ int i4 = i * 4;
+
+ indexBufferData[i6 + 0] = i4 + 0;
+ indexBufferData[i6 + 1] = i4 + 1;
+ indexBufferData[i6 + 2] = i4 + 2;
+
+ indexBufferData[i6 + 3] = i4 + 0;
+ indexBufferData[i6 + 4] = i4 + 2;
+ indexBufferData[i6 + 5] = i4 + 3;
+ }
+
+ glGenBuffers(1, &mIndexBufferID);
+ glBindBuffer(GL_ARRAY_BUFFER, mIndexBufferID);
+ glBufferData(GL_ARRAY_BUFFER, indexBufferSizeBytes, indexBufferData, GL_DYNAMIC_DRAW);
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+
+ free(indexBufferData);
+
+ uint32_t coordSize = 3;
+ uint32_t uvSize = 2;
+ uint32_t vertsPerQuad = 4;
+ uint32_t vertexBufferSizeBytes = mMaxNumberOfQuads * vertsPerQuad * coordSize *
+ uvSize * sizeof(float);
+ mTextMeshPtr = (float*) malloc(vertexBufferSizeBytes);
+}
+
+// We don't want to allocate anything unless we actually draw text
+void FontRenderer::checkInit() {
+ if (mInitialized) {
+ return;
+ }
+
+ initTextTexture();
+ initVertexArrayBuffers();
+
+ mInitialized = true;
+}
+
+void FontRenderer::issueDrawCommand() {
+ if (mUploadTexture) {
+ glBindTexture(GL_TEXTURE_2D, mTextureId);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, mCacheWidth, mCacheHeight, 0, GL_ALPHA,
+ GL_UNSIGNED_BYTE, mTextTexture);
+ mUploadTexture = false;
+ }
+
+ float *vtx = mTextMeshPtr;
+ float *tex = vtx + 3;
+
+ // position is slot 0
+ uint32_t slot = 0;
+ glVertexAttribPointer(slot, 3, GL_FLOAT, false, 20, vtx);
+
+ // texture0 is slot 1
+ slot = 1;
+ glVertexAttribPointer(slot, 2, GL_FLOAT, false, 20, tex);
+
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBufferID);
+ glDrawElements(GL_TRIANGLES, mCurrentQuadIndex * 6, GL_UNSIGNED_SHORT, NULL);
+}
+
+void FontRenderer::appendMeshQuad(float x1, float y1, float z1, float u1, float v1, float x2,
+ float y2, float z2, float u2, float v2, float x3, float y3, float z3, float u3, float v3,
+ float x4, float y4, float z4, float u4, float v4) {
+ const uint32_t vertsPerQuad = 4;
+ const uint32_t floatsPerVert = 5;
+ float *currentPos = mTextMeshPtr + mCurrentQuadIndex * vertsPerQuad * floatsPerVert;
+
+ // TODO: Cull things that are off the screen
+ // float width = (float)mRSC->getWidth();
+ // float height = (float)mRSC->getHeight();
+ //
+ // if(x1 > width || y1 < 0.0f || x2 < 0 || y4 > height) {
+ // return;
+ // }
+
+ (*currentPos++) = x1;
+ (*currentPos++) = y1;
+ (*currentPos++) = z1;
+ (*currentPos++) = u1;
+ (*currentPos++) = v1;
+
+ (*currentPos++) = x2;
+ (*currentPos++) = y2;
+ (*currentPos++) = z2;
+ (*currentPos++) = u2;
+ (*currentPos++) = v2;
+
+ (*currentPos++) = x3;
+ (*currentPos++) = y3;
+ (*currentPos++) = z3;
+ (*currentPos++) = u3;
+ (*currentPos++) = v3;
+
+ (*currentPos++) = x4;
+ (*currentPos++) = y4;
+ (*currentPos++) = z4;
+ (*currentPos++) = u4;
+ (*currentPos++) = v4;
+
+ mCurrentQuadIndex++;
+
+ if (mCurrentQuadIndex == mMaxNumberOfQuads) {
+ issueDrawCommand();
+ mCurrentQuadIndex = 0;
+ }
+}
+
+void FontRenderer::setFont(uint32_t fontId, float fontSize) {
+ mCurrentFont = Font::create(this, fontId, fontSize);
+}
+
+void FontRenderer::renderText(SkPaint* paint, const char *text, uint32_t len, uint32_t startIndex,
+ int numGlyphs, int x, int y) {
+ checkInit();
+
+ // Render code here
+ Font *currentFont = mCurrentFont;
+ if (!currentFont) {
+ LOGE("Unable to initialize any fonts");
+ return;
+ }
+
+ currentFont->renderUTF(paint, text, len, startIndex, numGlyphs, x, y);
+
+ if (mCurrentQuadIndex != 0) {
+ issueDrawCommand();
+ mCurrentQuadIndex = 0;
+ }
+}
+
+void FontRenderer::renderText(SkPaint* paint, const char *text, int x, int y) {
+ size_t textLen = strlen(text);
+ renderText(paint, text, textLen, 0, -1, x, y);
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/FontRenderer.h b/libs/hwui/FontRenderer.h
new file mode 100644
index 0000000..c18327a
--- /dev/null
+++ b/libs/hwui/FontRenderer.h
@@ -0,0 +1,184 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_UI_FONT_RENDERER_H
+#define ANDROID_UI_FONT_RENDERER_H
+
+#include <utils/String8.h>
+#include <utils/Vector.h>
+#include <utils/KeyedVector.h>
+
+#include <SkScalerContext.h>
+#include <SkPaint.h>
+
+#include <GLES2/gl2.h>
+
+namespace android {
+namespace uirenderer {
+
+class FontRenderer;
+
+class Font {
+public:
+ ~Font();
+
+ // Pointer to the utf data, length of data, where to start, number of glyphs ot read
+ // (each glyph may be longer than a char because we are dealing with utf data)
+ // Last two variables are the initial pen position
+ void renderUTF(SkPaint* paint, const char *text, uint32_t len, uint32_t start,
+ int numGlyphs, int x, int y);
+
+ static Font* create(FontRenderer* state, uint32_t fontId, float fontSize);
+
+protected:
+
+ friend class FontRenderer;
+
+ void invalidateTextureCache();
+ struct CachedGlyphInfo {
+ // Has the cache been invalidated?
+ bool mIsValid;
+ // Location of the cached glyph in the bitmap
+ // in case we need to resize the texture
+ uint32_t mBitmapWidth;
+ uint32_t mBitmapHeight;
+ // Also cache texture coords for the quad
+ float mBitmapMinU;
+ float mBitmapMinV;
+ float mBitmapMaxU;
+ float mBitmapMaxV;
+ // Minimize how much we call freetype
+ uint32_t mGlyphIndex;
+ uint32_t mAdvanceX;
+ uint32_t mAdvanceY;
+ // Values below contain a glyph's origin in the bitmap
+ uint32_t mBitmapLeft;
+ uint32_t mBitmapTop;
+ };
+
+ FontRenderer* mState;
+ uint32_t mFontId;
+ float mFontSize;
+
+ Font(FontRenderer* state, uint32_t fontId, float fontSize);
+
+ DefaultKeyedVector<int32_t, CachedGlyphInfo*> mCachedGlyphs;
+
+ CachedGlyphInfo *cacheGlyph(SkPaint* paint, int32_t glyph);
+ void updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, CachedGlyphInfo *glyph);
+ void drawCachedGlyph(CachedGlyphInfo *glyph, int x, int y);
+};
+
+class FontRenderer {
+public:
+ FontRenderer();
+ ~FontRenderer();
+
+ void init();
+ void deinit();
+
+ void setFont(uint32_t fontId, float fontSize);
+ void renderText(SkPaint* paint, const char *text, uint32_t len, uint32_t startIndex,
+ int numGlyphs, int x, int y);
+ void renderText(SkPaint* paint, const char *text, int x, int y);
+
+ GLuint getTexture() {
+ checkInit();
+ return mTextureId;
+ }
+
+protected:
+ friend class Font;
+
+ struct CacheTextureLine {
+ uint16_t mMaxHeight;
+ uint16_t mMaxWidth;
+ uint32_t mCurrentRow;
+ uint32_t mCurrentCol;
+
+ CacheTextureLine(uint16_t maxHeight, uint16_t maxWidth, uint32_t currentRow,
+ uint32_t currentCol):
+ mMaxHeight(maxHeight), mMaxWidth(maxWidth), mCurrentRow(currentRow),
+ mCurrentCol(currentCol) {
+ }
+
+ bool fitBitmap(const SkGlyph& glyph, uint32_t *retOriginX, uint32_t *retOriginY) {
+ if (glyph.fHeight > mMaxHeight) {
+ return false;
+ }
+
+ if (mCurrentCol + glyph.fWidth < mMaxWidth) {
+ *retOriginX = mCurrentCol;
+ *retOriginY = mCurrentRow;
+ mCurrentCol += glyph.fWidth;
+ return true;
+ }
+
+ return false;
+ }
+ };
+
+ uint32_t getCacheWidth() const {
+ return mCacheWidth;
+ }
+
+ uint32_t getCacheHeight() const {
+ return mCacheHeight;
+ }
+
+ void initTextTexture();
+
+ bool cacheBitmap(const SkGlyph& glyph, uint32_t *retOriginX, uint32_t *retOriginY);
+
+ void flushAllAndInvalidate();
+ void initVertexArrayBuffers();
+
+ void checkInit();
+
+ void issueDrawCommand();
+
+ void appendMeshQuad(float x1, float y1, float z1, float u1, float v1, float x2, float y2,
+ float z2, float u2, float v2, float x3, float y3, float z3, float u3, float v3,
+ float x4, float y4, float z4, float u4, float v4);
+
+ uint32_t mCacheWidth;
+ uint32_t mCacheHeight;
+
+ Font* mCurrentFont;
+
+ Vector<CacheTextureLine*> mCacheLines;
+
+ Vector<Font*> mActiveFonts;
+
+ // Texture to cache glyph bitmaps
+ unsigned char* mTextTexture;
+ GLuint mTextureId;
+ bool mUploadTexture;
+
+ // Pointer to vertex data to speed up frame to frame work
+ float *mTextMeshPtr;
+ uint32_t mCurrentQuadIndex;
+ uint32_t mMaxNumberOfQuads;
+
+ uint32_t mIndexBufferID;
+
+ bool mInitialized;
+};
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_UI_FONT_RENDERER_H
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 1fa76d2..8f04d92 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -21,6 +21,7 @@
#include <sys/types.h>
#include <SkCanvas.h>
+#include <SkTypeface.h>
#include <cutils/properties.h>
#include <utils/Log.h>
@@ -134,6 +135,7 @@
mDrawColorProgram = new DrawColorProgram;
mDrawTextureProgram = new DrawTextureProgram;
+ mDrawTextProgram = new DrawTextProgram;
mDrawLinearGradientProgram = new DrawLinearGradientProgram;
mCurrentProgram = mDrawTextureProgram;
@@ -527,6 +529,39 @@
drawColorRect(left, top, right, bottom, color, mode);
}
+void OpenGLRenderer::drawText(const char* text, int count, float x, float y, SkPaint* paint) {
+ // TODO: Support paint's text alignments, proper clipping
+ if (quickReject(x, y, x + 1, y +1)) {
+ return;
+ }
+
+ int alpha;
+ SkXfermode::Mode mode;
+ getAlphaAndMode(paint, &alpha, &mode);
+
+ uint32_t color = paint->getColor();
+ const GLfloat a = alpha / 255.0f;
+ const GLfloat r = a * ((color >> 16) & 0xFF) / 255.0f;
+ const GLfloat g = a * ((color >> 8) & 0xFF) / 255.0f;
+ const GLfloat b = a * ((color ) & 0xFF) / 255.0f;
+
+ mModelView.loadIdentity();
+
+ useProgram(mDrawTextProgram);
+ mDrawTextProgram->set(mOrthoMatrix, mModelView, mSnapshot->transform);
+
+ chooseBlending(true, mode);
+ bindTexture(mFontRenderer.getTexture(), GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE);
+
+ // Always premultiplied
+ glUniform4f(mDrawTextProgram->color, r, g, b, a);
+
+ mFontRenderer.setFont(SkTypeface::UniqueID(paint->getTypeface()), paint->getTextSize());
+ mFontRenderer.renderText(paint, text, count, 0, count, x, y);
+
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+}
+
///////////////////////////////////////////////////////////////////////////////
// Shaders
///////////////////////////////////////////////////////////////////////////////
@@ -687,6 +722,7 @@
float u2 = right - left;
float v2 = bottom - top;
+ // TODO: If the texture is not pow, use a shader to support repeat/mirror
if (mShaderMatrix) {
SkMatrix inverse;
mShaderMatrix->invert(&inverse);
@@ -742,7 +778,6 @@
bindTexture(texture, mShaderTileX, mShaderTileY);
// Always premultiplied
- //glUniform4f(mDrawTextureProgram->color, alpha, alpha, alpha, alpha);
glUniform4f(mDrawTextureProgram->color, alpha, alpha, alpha, alpha);
glVertexAttribPointer(mDrawTextureProgram->position, 2, GL_FLOAT, GL_FALSE,
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index 9dc2a43..b82366b 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -39,6 +39,7 @@
#include "GradientCache.h"
#include "PatchCache.h"
#include "Vertex.h"
+#include "FontRenderer.h"
namespace android {
namespace uirenderer {
@@ -108,6 +109,8 @@
float* positions, int count, SkShader::TileMode tileMode,
SkMatrix* matrix, bool hasAlpha);
+ void drawText(const char* text, int count, float x, float y, SkPaint* paint);
+
private:
/**
* Type of Skia shader in use.
@@ -329,6 +332,7 @@
sp<Program> mCurrentProgram;
sp<DrawColorProgram> mDrawColorProgram;
sp<DrawTextureProgram> mDrawTextureProgram;
+ sp<DrawTextProgram> mDrawTextProgram;
sp<DrawLinearGradientProgram> mDrawLinearGradientProgram;
// Used to draw textured quads
@@ -357,6 +361,9 @@
float* mShaderPositions;
int mShaderCount;
+ // Font renderer
+ FontRenderer mFontRenderer;
+
// Various caches
TextureCache mTextureCache;
LayerCache mLayerCache;
diff --git a/libs/hwui/Program.cpp b/libs/hwui/Program.cpp
index 841b6c8..6e60808 100644
--- a/libs/hwui/Program.cpp
+++ b/libs/hwui/Program.cpp
@@ -33,6 +33,8 @@
#include "shaders/drawTexture.vert"
#include "shaders/drawTexture.frag"
+#include "shaders/drawText.frag"
+
#include "shaders/drawLinearGradient.vert"
#include "shaders/drawLinearGradient.frag"
@@ -169,6 +171,12 @@
sampler = addUniform("sampler");
}
+DrawTextureProgram::DrawTextureProgram(const char* vertex, const char* fragment):
+ DrawColorProgram(vertex, fragment) {
+ texCoords = addAttrib("texCoords");
+ sampler = addUniform("sampler");
+}
+
void DrawTextureProgram::use() {
DrawColorProgram::use();
glActiveTexture(GL_TEXTURE0);
@@ -182,6 +190,14 @@
}
///////////////////////////////////////////////////////////////////////////////
+// Draw text
+///////////////////////////////////////////////////////////////////////////////
+
+DrawTextProgram::DrawTextProgram():
+ DrawTextureProgram(gDrawTextureVertexShader, gDrawTextFragmentShader) {
+}
+
+///////////////////////////////////////////////////////////////////////////////
// Draw linear gradient
///////////////////////////////////////////////////////////////////////////////
diff --git a/libs/hwui/Program.h b/libs/hwui/Program.h
index 18a8e92..824aa05 100644
--- a/libs/hwui/Program.h
+++ b/libs/hwui/Program.h
@@ -167,6 +167,7 @@
class DrawTextureProgram: public DrawColorProgram {
public:
DrawTextureProgram();
+ DrawTextureProgram(const char* vertex, const char* fragment);
/**
* Binds this program to the GL context.
@@ -190,6 +191,11 @@
int texCoords;
};
+class DrawTextProgram: public DrawTextureProgram {
+public:
+ DrawTextProgram();
+};
+
/**
* Program used to draw linear gradients. In addition to everything that the
* DrawColorProgram supports, the following two attributes must be specified:
diff --git a/libs/hwui/shaders/drawText.frag b/libs/hwui/shaders/drawText.frag
new file mode 100644
index 0000000..49532c7
--- /dev/null
+++ b/libs/hwui/shaders/drawText.frag
@@ -0,0 +1,14 @@
+SHADER_SOURCE(gDrawTextFragmentShader,
+
+precision mediump float;
+
+varying vec2 outTexCoords;
+
+uniform vec4 color;
+uniform sampler2D sampler;
+
+void main(void) {
+ gl_FragColor = color * texture2D(sampler, outTexCoords).a;
+}
+
+);
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index 098359c..8cb9e0d 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -105,6 +105,16 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
+
+ <activity
+ android:name="TextActivity"
+ android:label="_Text"
+ android:theme="@android:style/Theme.NoTitleBar">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
</application>
</manifest>
diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/TextActivity.java b/tests/HwAccelerationTest/src/com/google/android/test/hwui/TextActivity.java
new file mode 100644
index 0000000..6665ef5
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/google/android/test/hwui/TextActivity.java
@@ -0,0 +1,59 @@
+/*
+ * 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.google.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 TextActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(new CustomTextView(this));
+ }
+
+ static class CustomTextView extends View {
+ private final Paint mMediumPaint;
+ private final Paint mLargePaint;
+
+ CustomTextView(Context c) {
+ super(c);
+
+ mMediumPaint = new Paint();
+ mMediumPaint.setAntiAlias(true);
+ mMediumPaint.setColor(0xffff0000);
+ mLargePaint = new Paint();
+ mLargePaint.setAntiAlias(true);
+ mLargePaint.setTextSize(36.0f);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ canvas.drawRGB(255, 255, 255);
+
+ canvas.drawText("Hello OpenGL renderer!", 100, 100, mMediumPaint);
+ canvas.drawText("Hello OpenGL renderer!", 100, 200, mLargePaint);
+ }
+ }
+}
\ No newline at end of file