summaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
authorFabrice Di Meglio <fdimeglio@google.com>2011-02-24 19:56:18 -0800
committerFabrice Di Meglio <fdimeglio@google.com>2011-03-02 13:29:20 -0800
commitd313c665e618af3194f504064bcd284fe5368682 (patch)
treeafc941c310913e7ec79af8c50fb152b1281ba2ff /core
parent5fb008e3800032be18c6d5f1e7ace4d7c2661e26 (diff)
downloadframeworks_base-d313c665e618af3194f504064bcd284fe5368682.zip
frameworks_base-d313c665e618af3194f504064bcd284fe5368682.tar.gz
frameworks_base-d313c665e618af3194f504064bcd284fe5368682.tar.bz2
Add TextLayout Cache
- use GenerationCache for caching - move GenerationCache.h from libs/hwui/utils to include/utils - add #define for cache activation / deactivation Change-Id: Ifaf519f0b5e33b087a453e4aa6430162d8438f20
Diffstat (limited to 'core')
-rw-r--r--core/jni/Android.mk1
-rw-r--r--core/jni/android/graphics/RtlProperties.h49
-rw-r--r--core/jni/android/graphics/TextLayout.cpp101
-rw-r--r--core/jni/android/graphics/TextLayout.h28
-rw-r--r--core/jni/android/graphics/TextLayoutCache.cpp210
-rw-r--r--core/jni/android/graphics/TextLayoutCache.h314
6 files changed, 614 insertions, 89 deletions
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 41baca2..12eda8e 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -112,6 +112,7 @@ LOCAL_SRC_FILES:= \
android/graphics/Shader.cpp \
android/graphics/SurfaceTexture.cpp \
android/graphics/TextLayout.cpp \
+ android/graphics/TextLayoutCache.cpp \
android/graphics/Typeface.cpp \
android/graphics/Utils.cpp \
android/graphics/Xfermode.cpp \
diff --git a/core/jni/android/graphics/RtlProperties.h b/core/jni/android/graphics/RtlProperties.h
new file mode 100644
index 0000000..6d8ba91
--- /dev/null
+++ b/core/jni/android/graphics/RtlProperties.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2011 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_RTL_PROPERTIES_H
+#define ANDROID_RTL_PROPERTIES_H
+
+#include <cutils/properties.h>
+#include <stdlib.h>
+
+namespace android {
+
+/**
+ * Debug level for app developers.
+ */
+#define RTL_PROPERTY_DEBUG "rtl.debug_level"
+
+/**
+ * Debug levels. Debug levels are used as flags.
+ */
+enum RtlDebugLevel {
+ kRtlDebugDisabled = 0,
+ kRtlDebugMemory = 1,
+ kRtlDebugCaches = 2,
+ kRtlDebugAllocations = 3
+};
+
+static RtlDebugLevel readRtlDebugLevel() {
+ char property[PROPERTY_VALUE_MAX];
+ if (property_get(RTL_PROPERTY_DEBUG, property, NULL) > 0) {
+ return (RtlDebugLevel) atoi(property);
+ }
+ return kRtlDebugDisabled;
+}
+
+} // namespace android
+#endif // ANDROID_RTL_PROPERTIES_H
diff --git a/core/jni/android/graphics/TextLayout.cpp b/core/jni/android/graphics/TextLayout.cpp
index e957635..f1bb696 100644
--- a/core/jni/android/graphics/TextLayout.cpp
+++ b/core/jni/android/graphics/TextLayout.cpp
@@ -15,6 +15,7 @@
*/
#include "TextLayout.h"
+#include "TextLayoutCache.h"
#include <android_runtime/AndroidRuntime.h>
@@ -23,10 +24,12 @@
#include "unicode/ushape.h"
#include <utils/Log.h>
-// Log debug messages from RTL related allocations
-#define DEBUG_RTL_ALLOCATIONS 0
-
namespace android {
+
+#if USE_TEXT_LAYOUT_CACHE
+TextLayoutCache TextLayout::mCache;
+#endif
+
// Returns true if we might need layout. If bidiFlags force LTR, assume no layout, if
// bidiFlags indicate there probably is RTL, assume we do, otherwise scan the text
// looking for a character >= the first RTL character in unicode and assume we do if
@@ -60,14 +63,10 @@ bool TextLayout::needsLayout(const jchar* text, jint len, jint bidiFlags) {
* @return the length of the shaped text, or -1 if error
*/
int TextLayout::shapeRtlText(const jchar* context, jsize start, jsize count, jsize contextCount,
- jchar* shaped, UErrorCode &status) {
+ jchar* shaped, UErrorCode& status) {
SkAutoSTMalloc<CHAR_BUFFER_SIZE, jchar> tempBuffer(contextCount);
jchar* buffer = tempBuffer.get();
-#if DEBUG_RTL_ALLOCATIONS
- LOGD("TextLayout::shapeRtlText - allocated buffer with size: %d", contextCount);
-#endif
-
// Use fixed length since we need to keep start and count valid
u_shapeArabic(context, contextCount, buffer, contextCount,
U_SHAPE_LENGTH_FIXED_SPACES_NEAR |
@@ -105,8 +104,8 @@ int TextLayout::shapeRtlText(const jchar* context, jsize start, jsize count, jsi
* @flags line bidi flags
* @return the length of the reordered, shaped line, or -1 if error
*/
-jint TextLayout::layoutLine(const jchar* text, jint len, jint flags, int &dir, jchar* buffer,
- UErrorCode &status) {
+jint TextLayout::layoutLine(const jchar* text, jint len, jint flags, int& dir, jchar* buffer,
+ UErrorCode& status) {
static const int RTL_OPTS = UBIDI_DO_MIRRORING | UBIDI_KEEP_BASE_COMBINING |
UBIDI_REMOVE_BIDI_CONTROLS | UBIDI_OUTPUT_REVERSE;
@@ -156,7 +155,7 @@ jint TextLayout::layoutLine(const jchar* text, jint len, jint flags, int &dir, j
return result;
}
-bool TextLayout::prepareText(SkPaint *paint, const jchar* text, jsize len, jint bidiFlags,
+bool TextLayout::prepareText(SkPaint* paint, const jchar* text, jsize len, jint bidiFlags,
const jchar** outText, int32_t* outBytes, jchar** outBuffer) {
const jchar *workText = text;
jchar *buffer = NULL;
@@ -166,11 +165,6 @@ bool TextLayout::prepareText(SkPaint *paint, const jchar* text, jsize len, jint
if (!buffer) {
return false;
}
-
-#if DEBUG_RTL_ALLOCATIONS
- LOGD("TextLayout::prepareText - allocated buffer with size: %d", len);
-#endif
-
UErrorCode status = U_ZERO_ERROR;
len = layoutLine(text, len, bidiFlags, dir, buffer, status); // might change len, dir
if (!U_SUCCESS(status)) {
@@ -178,7 +172,6 @@ bool TextLayout::prepareText(SkPaint *paint, const jchar* text, jsize len, jint
free(buffer);
return false; // can't render
}
-
workText = buffer; // use the shaped text
}
@@ -262,74 +255,24 @@ void TextLayout::drawTextRun(SkPaint* paint, const jchar* chars,
}
}
-void TextLayout::getTextRunAdvances(SkPaint *paint, const jchar *chars, jint start,
+void TextLayout::getTextRunAdvances(SkPaint* paint, const jchar* chars, jint start,
jint count, jint contextCount, jint dirFlags,
- jfloat *resultAdvances, jfloat &resultTotalAdvance) {
- resultTotalAdvance = 0;
-
- SkAutoSTMalloc<CHAR_BUFFER_SIZE, jchar> tempBuffer(contextCount);
- jchar* buffer = tempBuffer.get();
-
-#if DEBUG_RTL_ALLOCATIONS
- LOGD("TextLayout::getTextRunAdvances - allocated buffer with size: %d", contextCount);
+ jfloat* resultAdvances, jfloat& resultTotalAdvance) {
+#if USE_TEXT_LAYOUT_CACHE
+ // Return advances from the cache. Compute them if needed
+ mCache.getRunAdvances(paint, chars, start, count, contextCount,
+ dirFlags, resultAdvances, &resultTotalAdvance);
+#else
+ // Compute advances and return them
+ RunAdvanceDescription::computeAdvances(paint, chars, start, count, contextCount, dirFlags,
+ resultAdvances, &resultTotalAdvance);
#endif
-
- SkScalar* scalarArray = (SkScalar*)resultAdvances;
-
- // this is where we'd call harfbuzz
- // for now we just use ushape.c
-
- int widths;
- const jchar* text;
- if (dirFlags & 0x1) { // rtl, call arabic shaping in case
- UErrorCode status = U_ZERO_ERROR;
- // Use fixed length since we need to keep start and count valid
- u_shapeArabic(chars, contextCount, buffer, contextCount,
- U_SHAPE_LENGTH_FIXED_SPACES_NEAR |
- U_SHAPE_TEXT_DIRECTION_LOGICAL | U_SHAPE_LETTERS_SHAPE |
- U_SHAPE_X_LAMALEF_SUB_ALTERNATE, &status);
- // we shouldn't fail unless there's an out of memory condition,
- // in which case we're hosed anyway
- for (int i = start, e = i + count; i < e; ++i) {
- if (buffer[i] == UNICODE_NOT_A_CHAR) {
- buffer[i] = UNICODE_ZWSP; // zero-width-space for skia
- }
- }
- text = buffer + start;
- widths = paint->getTextWidths(text, count << 1, scalarArray);
- } else {
- text = chars + start;
- widths = paint->getTextWidths(text, count << 1, scalarArray);
- }
-
- if (widths < count) {
- // Skia operates on code points, not code units, so surrogate pairs return only
- // one value. Expand the result so we have one value per UTF-16 code unit.
-
- // Note, skia's getTextWidth gets confused if it encounters a surrogate pair,
- // leaving the remaining widths zero. Not nice.
- for (int i = 0, p = 0; i < widths; ++i) {
- resultTotalAdvance += resultAdvances[p++] = SkScalarToFloat(scalarArray[i]);
- if (p < count &&
- text[p] >= UNICODE_FIRST_LOW_SURROGATE &&
- text[p] < UNICODE_FIRST_PRIVATE_USE &&
- text[p-1] >= UNICODE_FIRST_HIGH_SURROGATE &&
- text[p-1] < UNICODE_FIRST_LOW_SURROGATE) {
- resultAdvances[p++] = 0;
- }
- }
- } else {
- for (int i = 0; i < count; i++) {
- resultTotalAdvance += resultAdvances[i] = SkScalarToFloat(scalarArray[i]);
- }
- }
}
// Draws a paragraph of text on a single line, running bidi and shaping
void TextLayout::drawText(SkPaint* paint, const jchar* text, jsize len,
int bidiFlags, jfloat x, jfloat y, SkCanvas* canvas) {
-
handleText(paint, text, len, bidiFlags, x, y, canvas, NULL);
}
@@ -353,10 +296,6 @@ void TextLayout::drawTextOnPath(SkPaint* paint, const jchar* text, int count,
SkAutoSTMalloc<CHAR_BUFFER_SIZE, jchar> buffer(count);
-#if DEBUG_RTL_ALLOCATIONS
- LOGD("TextLayout::drawTextOnPath - allocated buffer with size: %d", count);
-#endif
-
int dir = kDirection_LTR;
UErrorCode status = U_ZERO_ERROR;
count = layoutLine(text, count, bidiFlags, dir, buffer.get(), status);
diff --git a/core/jni/android/graphics/TextLayout.h b/core/jni/android/graphics/TextLayout.h
index c98f745..a950d13 100644
--- a/core/jni/android/graphics/TextLayout.h
+++ b/core/jni/android/graphics/TextLayout.h
@@ -20,6 +20,8 @@
#include "SkPaint.h"
#include "unicode/utypes.h"
+#include "TextLayoutCache.h"
+
namespace android {
#define UNICODE_NOT_A_CHAR 0xffff
@@ -34,6 +36,11 @@ namespace android {
*/
#define CHAR_BUFFER_SIZE 80
+/**
+ * Turn on for using the Cache
+ */
+#define USE_TEXT_LAYOUT_CACHE 1
+
class TextLayout {
public:
@@ -62,22 +69,23 @@ public:
jint start, jint count, jint contextCount,
int dirFlags, jfloat x, jfloat y, SkCanvas* canvas);
- static void getTextRunAdvances(SkPaint *paint, const jchar *chars, jint start,
+ static void getTextRunAdvances(SkPaint* paint, const jchar* chars, jint start,
jint count, jint contextCount, jint dirFlags,
- jfloat *resultAdvances, jfloat &resultTotalAdvance);
+ jfloat* resultAdvances, jfloat& resultTotalAdvance);
static void drawText(SkPaint* paint, const jchar* text, jsize len,
jint bidiFlags, jfloat x, jfloat y, SkCanvas* canvas);
- static void getTextPath(SkPaint *paint, const jchar *text, jsize len,
- jint bidiFlags, jfloat x, jfloat y, SkPath *path);
+ static void getTextPath(SkPaint* paint, const jchar* text, jsize len,
+ jint bidiFlags, jfloat x, jfloat y, SkPath* path);
static void drawTextOnPath(SkPaint* paint, const jchar* text, jsize len,
int bidiFlags, jfloat hOffset, jfloat vOffset,
SkPath* path, SkCanvas* canvas);
- static bool prepareText(SkPaint *paint, const jchar* text, jsize len, jint bidiFlags,
+ static bool prepareText(SkPaint* paint, const jchar* text, jsize len, jint bidiFlags,
const jchar** outText, int32_t* outBytes, jchar** outBuffer);
+
static bool prepareRtlTextRun(const jchar* context, jsize start, jsize& count,
jsize contextCount, jchar* shaped);
@@ -85,11 +93,15 @@ public:
private:
static bool needsLayout(const jchar* text, jint len, jint bidiFlags);
static int shapeRtlText(const jchar* context, jsize start, jsize count, jsize contextCount,
- jchar* shaped, UErrorCode &status);
+ jchar* shaped, UErrorCode& status);
static jint layoutLine(const jchar* text, jint len, jint flags, int &dir, jchar* buffer,
UErrorCode &status);
- static void handleText(SkPaint *paint, const jchar* text, jsize len,
- int bidiFlags, jfloat x, jfloat y,SkCanvas *canvas, SkPath *path);
+ static void handleText(SkPaint* paint, const jchar* text, jsize len,
+ int bidiFlags, jfloat x, jfloat y, SkCanvas* canvas, SkPath* path);
+
+#if USE_TEXT_LAYOUT_CACHE
+ static TextLayoutCache mCache;
+#endif
};
}
diff --git a/core/jni/android/graphics/TextLayoutCache.cpp b/core/jni/android/graphics/TextLayoutCache.cpp
new file mode 100644
index 0000000..7888769
--- /dev/null
+++ b/core/jni/android/graphics/TextLayoutCache.cpp
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2011 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 "TextLayoutCache.h"
+
+namespace android {
+
+TextLayoutCache::TextLayoutCache():
+ mCache(GenerationCache<TextLayoutCacheKey, RunAdvanceDescription*>::kUnlimitedCapacity),
+ mSize(0), mMaxSize(MB(DEFAULT_TEXT_LAYOUT_CACHE_SIZE_IN_MB)),
+ mCacheHitCount(0), mNanosecondsSaved(0) {
+ init();
+}
+
+TextLayoutCache::TextLayoutCache(uint32_t max):
+ mCache(GenerationCache<TextLayoutCacheKey, RunAdvanceDescription*>::kUnlimitedCapacity),
+ mSize(0), mMaxSize(max),
+ mCacheHitCount(0), mNanosecondsSaved(0) {
+ init();
+}
+
+TextLayoutCache::~TextLayoutCache() {
+ mCache.clear();
+}
+
+void TextLayoutCache::init() {
+ mCache.setOnEntryRemovedListener(this);
+
+ mDebugLevel = readRtlDebugLevel();
+ mDebugEnabled = mDebugLevel & kRtlDebugCaches;
+ LOGD("Using TextLayoutCache debug level: %d - Debug Enabled: %d", mDebugLevel, mDebugEnabled);
+
+ mCacheStartTime = systemTime(SYSTEM_TIME_MONOTONIC);
+ if (mDebugEnabled) {
+ LOGD("TextLayoutCache start time: %lld", mCacheStartTime);
+ }
+
+ mInitialized = true;
+ if (mDebugEnabled) {
+ LOGD("TextLayoutCache initialization is done");
+ }
+}
+
+/*
+ * Size management
+ */
+
+uint32_t TextLayoutCache::getSize() {
+ return mSize;
+}
+
+uint32_t TextLayoutCache::getMaxSize() {
+ return mMaxSize;
+}
+
+void TextLayoutCache::setMaxSize(uint32_t maxSize) {
+ mMaxSize = maxSize;
+ removeOldests();
+}
+
+void TextLayoutCache::removeOldests() {
+ while (mSize > mMaxSize) {
+ mCache.removeOldest();
+ }
+}
+
+/**
+ * Callbacks
+ */
+void TextLayoutCache::operator()(TextLayoutCacheKey& text, RunAdvanceDescription*& desc) {
+ if (desc) {
+ size_t totalSizeToDelete = text.getSize() + desc->getSize();
+ mSize -= totalSizeToDelete;
+ if (mDebugEnabled) {
+ LOGD("RunAdvance description deleted, size = %d", totalSizeToDelete);
+ }
+ delete desc;
+ }
+}
+
+/*
+ * Cache clearing
+ */
+void TextLayoutCache::clear() {
+ mCache.clear();
+}
+
+/*
+ * Caching
+ */
+void TextLayoutCache::getRunAdvances(SkPaint* paint, const jchar* text,
+ jint start, jint count, jint contextCount, jint dirFlags,
+ jfloat* outAdvances, jfloat* outTotalAdvance) {
+
+ AutoMutex _l(mLock);
+
+ nsecs_t startTime = 0;
+ if (mDebugEnabled) {
+ startTime = systemTime(SYSTEM_TIME_MONOTONIC);
+ }
+
+ TextLayoutCacheKey entry(paint, text, start, count, contextCount, dirFlags);
+
+ // Get entry for cache if possible
+ RunAdvanceDescription* desc = mCache.get(entry);
+
+ // Value not found for the entry, we need to add a new value in the cache
+ if (!desc) {
+ desc = new RunAdvanceDescription();
+
+ // Compute advances and store them
+ desc->computeAdvances(paint, text, start, count, contextCount, dirFlags);
+ desc->copyResult(outAdvances, outTotalAdvance);
+
+ // Don't bother to add in the cache if the entry is too big
+ size_t size = entry.getSize() + desc->getSize();
+ if (size <= mMaxSize) {
+ // Cleanup to make some room if needed
+ if (mSize + size > mMaxSize) {
+ if (mDebugEnabled) {
+ LOGD("TextLayoutCache: need to clean some entries "
+ "for making some room for a new entry");
+ }
+ while (mSize + size > mMaxSize) {
+ // This will call the callback
+ mCache.removeOldest();
+ }
+ }
+
+ // Update current cache size
+ mSize += size;
+
+ // Copy the text when we insert the new entry
+ entry.internalTextCopy();
+ mCache.put(entry, desc);
+
+ if (mDebugEnabled) {
+ // Update timing information for statistics.
+ desc->setElapsedTime(systemTime(SYSTEM_TIME_MONOTONIC) - startTime);
+
+ LOGD("CACHE MISS: Added entry for text='%s' with start=%d, count=%d, "
+ "contextCount=%d, entry size %d bytes, remaining space %d bytes"
+ " - Compute time in nanos: %d",
+ String8(text, contextCount).string(), start, count, contextCount,
+ size, mMaxSize - mSize, desc->getElapsedTime());
+ }
+ } else {
+ if (mDebugEnabled) {
+ LOGD("CACHE MISS: Calculated but not storing entry because it is too big "
+ "for text='%s' with start=%d, count=%d, contextCount=%d, "
+ "entry size %d bytes, remaining space %d bytes"
+ " - Compute time in nanos: %d",
+ String8(text, contextCount).string(), start, count, contextCount,
+ size, mMaxSize - mSize, desc->getElapsedTime());
+ }
+ delete desc;
+ }
+ } else {
+ // This is a cache hit, just copy the pre-computed results
+ desc->copyResult(outAdvances, outTotalAdvance);
+ if (mDebugEnabled) {
+ nsecs_t elapsedTimeThruCacheGet = systemTime(SYSTEM_TIME_MONOTONIC) - startTime;
+ mNanosecondsSaved += (desc->getElapsedTime() - elapsedTimeThruCacheGet);
+ ++mCacheHitCount;
+
+ if (desc->getElapsedTime() > 0) {
+ float deltaPercent = 100 * ((desc->getElapsedTime() - elapsedTimeThruCacheGet)
+ / ((float)desc->getElapsedTime()));
+ LOGD("CACHE HIT #%d for text='%s' with start=%d, count=%d, contextCount=%d "
+ "- Compute time in nanos: %d - "
+ "Cache get time in nanos: %lld - Gain in percent: %2.2f",
+ mCacheHitCount, String8(text, contextCount).string(), start, count,
+ contextCount,
+ desc->getElapsedTime(), elapsedTimeThruCacheGet, deltaPercent);
+ }
+ if (mCacheHitCount % DEFAULT_DUMP_STATS_CACHE_HIT_INTERVAL == 0) {
+ dumpCacheStats();
+ }
+ }
+ }
+}
+
+void TextLayoutCache::dumpCacheStats() {
+ float remainingPercent = 100 * ((mMaxSize - mSize) / ((float)mMaxSize));
+ float timeRunningInSec = (systemTime(SYSTEM_TIME_MONOTONIC) - mCacheStartTime) / 1000000000;
+ LOGD("------------------------------------------------");
+ LOGD("TextLayoutCache stats");
+ LOGD("------------------------------------------------");
+ LOGD("running : %.0f seconds", timeRunningInSec);
+ LOGD("size : %d bytes", mMaxSize);
+ LOGD("remaining : %d bytes or %2.2f percent", mMaxSize - mSize, remainingPercent);
+ LOGD("hits : %d", mCacheHitCount);
+ LOGD("saved : %lld milliseconds", mNanosecondsSaved / 1000000);
+ LOGD("------------------------------------------------");
+}
+
+} // namespace android
diff --git a/core/jni/android/graphics/TextLayoutCache.h b/core/jni/android/graphics/TextLayoutCache.h
new file mode 100644
index 0000000..9d55918
--- /dev/null
+++ b/core/jni/android/graphics/TextLayoutCache.h
@@ -0,0 +1,314 @@
+/*
+ * Copyright (C) 2011 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_TEXT_LAYOUT_CACHE_H
+#define ANDROID_TEXT_LAYOUT_CACHE_H
+
+#include "RtlProperties.h"
+
+#include "stddef.h"
+#include <utils/threads.h>
+#include <utils/String16.h>
+#include "utils/GenerationCache.h"
+#include "utils/Compare.h"
+
+#include "SkPaint.h"
+#include "SkTemplates.h"
+
+#include "unicode/ubidi.h"
+#include "unicode/ushape.h"
+
+#include <android_runtime/AndroidRuntime.h>
+
+#define UNICODE_NOT_A_CHAR 0xffff
+#define UNICODE_ZWSP 0x200b
+#define UNICODE_FIRST_LOW_SURROGATE 0xdc00
+#define UNICODE_FIRST_HIGH_SURROGATE 0xd800
+#define UNICODE_FIRST_PRIVATE_USE 0xe000
+#define UNICODE_FIRST_RTL_CHAR 0x0590
+
+// Temporary buffer size
+#define CHAR_BUFFER_SIZE 80
+
+// Converts a number of mega-bytes into bytes
+#define MB(s) s * 1024 * 1024
+
+// Define the default cache size in Mb
+#define DEFAULT_TEXT_LAYOUT_CACHE_SIZE_IN_MB 0.125f
+
+// Define the interval in number of cache hits between two statistics dump
+#define DEFAULT_DUMP_STATS_CACHE_HIT_INTERVAL 100
+
+namespace android {
+
+/**
+ * TextLayoutCacheKey is the Cache key
+ */
+class TextLayoutCacheKey {
+public:
+ TextLayoutCacheKey() : text(NULL), start(0), count(0), contextCount(0),
+ dirFlags(0), textSize(0), typeface(NULL), textSkewX(0), fakeBoldText(false) {
+ }
+
+ TextLayoutCacheKey(const SkPaint* paint,
+ const UChar* text, size_t start, size_t count,
+ size_t contextCount, int dirFlags) :
+ text(text), start(start), count(count), contextCount(contextCount),
+ dirFlags(dirFlags) {
+ textSize = paint->getTextSize();
+ typeface = paint->getTypeface();
+ textSkewX = paint->getTextSkewX();
+ fakeBoldText = paint->isFakeBoldText();
+ }
+
+ bool operator<(const TextLayoutCacheKey& rhs) const {
+ LTE_INT(count) {
+ LTE_INT(contextCount) {
+ LTE_INT(start) {
+ LTE_FLOAT(textSize) {
+ LTE_INT(typeface) {
+ LTE_INT(textSkewX) {
+ LTE_INT(fakeBoldText) {
+ LTE_INT(dirFlags) {
+ return strncmp16(text, rhs.text, contextCount) < 0;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ // We need to copy the text when we insert the key into the cache itself.
+ // We don't need to copy the text when we are only comparing keys.
+ void internalTextCopy() {
+ textCopy.setTo(text, contextCount);
+ text = textCopy.string();
+ }
+
+ /**
+ * Get the size of the Cache key.
+ */
+ size_t getSize() {
+ return sizeof(TextLayoutCacheKey) + sizeof(UChar) * contextCount;
+ }
+
+private:
+ const UChar* text;
+ String16 textCopy;
+ size_t start;
+ size_t count;
+ size_t contextCount;
+ int dirFlags;
+ float textSize;
+ SkTypeface* typeface;
+ float textSkewX;
+ bool fakeBoldText;
+}; // TextLayoutCacheKey
+
+/*
+ * RunAdvanceDescription is the Cache entry
+ */
+class RunAdvanceDescription {
+public:
+ RunAdvanceDescription() {
+ advances = NULL;
+ totalAdvance = 0;
+ }
+
+ ~RunAdvanceDescription() {
+ delete[] advances;
+ }
+
+ void setElapsedTime(uint32_t time) {
+ elapsedTime = time;
+ }
+
+ uint32_t getElapsedTime() {
+ return elapsedTime;
+ }
+
+ void computeAdvances(SkPaint* paint, const UChar* chars, size_t start, size_t count,
+ size_t contextCount, int dirFlags) {
+ advances = new float[count];
+ this->count = count;
+
+ computeAdvances(paint, chars, start, count, contextCount, dirFlags,
+ advances, &totalAdvance);
+ }
+
+ void copyResult(jfloat* outAdvances, jfloat* outTotalAdvance) {
+ memcpy(outAdvances, advances, count * sizeof(jfloat));
+ *outTotalAdvance = totalAdvance;
+ }
+
+ /**
+ * Get the size of the Cache entry
+ */
+ size_t getSize() {
+ return sizeof(RunAdvanceDescription) + sizeof(jfloat) * count;
+ }
+
+ static void computeAdvances(SkPaint* paint, const UChar* chars, size_t start, size_t count,
+ size_t contextCount, int dirFlags, jfloat* outAdvances, jfloat* outTotalAdvance) {
+ SkAutoSTMalloc<CHAR_BUFFER_SIZE, jchar> tempBuffer(contextCount);
+ jchar* buffer = tempBuffer.get();
+
+ SkScalar* scalarArray = (SkScalar*)outAdvances;
+
+ // this is where we'd call harfbuzz
+ // for now we just use ushape.c
+ size_t widths;
+ const jchar* text;
+ if (dirFlags & 0x1) { // rtl, call arabic shaping in case
+ UErrorCode status = U_ZERO_ERROR;
+ // Use fixed length since we need to keep start and count valid
+ u_shapeArabic(chars, contextCount, buffer, contextCount,
+ U_SHAPE_LENGTH_FIXED_SPACES_NEAR |
+ U_SHAPE_TEXT_DIRECTION_LOGICAL | U_SHAPE_LETTERS_SHAPE |
+ U_SHAPE_X_LAMALEF_SUB_ALTERNATE, &status);
+ // we shouldn't fail unless there's an out of memory condition,
+ // in which case we're hosed anyway
+ for (int i = start, e = i + count; i < e; ++i) {
+ if (buffer[i] == UNICODE_NOT_A_CHAR) {
+ buffer[i] = UNICODE_ZWSP; // zero-width-space for skia
+ }
+ }
+ text = buffer + start;
+ widths = paint->getTextWidths(text, count << 1, scalarArray);
+ } else {
+ text = chars + start;
+ widths = paint->getTextWidths(text, count << 1, scalarArray);
+ }
+
+ jfloat totalAdvance = 0;
+ if (widths < count) {
+ // Skia operates on code points, not code units, so surrogate pairs return only
+ // one value. Expand the result so we have one value per UTF-16 code unit.
+
+ // Note, skia's getTextWidth gets confused if it encounters a surrogate pair,
+ // leaving the remaining widths zero. Not nice.
+ for (size_t i = 0, p = 0; i < widths; ++i) {
+ totalAdvance += outAdvances[p++] = SkScalarToFloat(scalarArray[i]);
+ if (p < count &&
+ text[p] >= UNICODE_FIRST_LOW_SURROGATE &&
+ text[p] < UNICODE_FIRST_PRIVATE_USE &&
+ text[p-1] >= UNICODE_FIRST_HIGH_SURROGATE &&
+ text[p-1] < UNICODE_FIRST_LOW_SURROGATE) {
+ outAdvances[p++] = 0;
+ }
+ }
+ } else {
+ for (size_t i = 0; i < count; i++) {
+ totalAdvance += outAdvances[i] = SkScalarToFloat(scalarArray[i]);
+ }
+ }
+ *outTotalAdvance = totalAdvance;
+ }
+
+private:
+ jfloat* advances;
+ jfloat totalAdvance;
+ size_t count;
+
+ uint32_t elapsedTime;
+}; // RunAdvanceDescription
+
+
+class TextLayoutCache: public OnEntryRemoved<TextLayoutCacheKey, RunAdvanceDescription*>
+{
+public:
+ TextLayoutCache();
+ TextLayoutCache(uint32_t maxByteSize);
+
+ virtual ~TextLayoutCache();
+
+ bool isInitialized() {
+ return mInitialized;
+ }
+
+ /**
+ * Used as a callback when an entry is removed from the cache.
+ * Do not invoke directly.
+ */
+ void operator()(TextLayoutCacheKey& text, RunAdvanceDescription*& desc);
+
+ /**
+ * Get cache entries
+ */
+ void getRunAdvances(SkPaint* paint, const jchar* text,
+ jint start, jint count, jint contextCount, jint dirFlags,
+ jfloat* outAdvances, jfloat* outTotalAdvance);
+
+ /**
+ * Clear the cache
+ */
+ void clear();
+
+ /**
+ * Sets the maximum size of the cache in bytes.
+ */
+ void setMaxSize(uint32_t maxSize);
+
+ /**
+ * Returns the maximum size of the cache in bytes.
+ */
+ uint32_t getMaxSize();
+
+ /**
+ * Returns the current size of the cache in bytes.
+ */
+ uint32_t getSize();
+
+private:
+ Mutex mLock;
+ bool mInitialized;
+
+ GenerationCache<TextLayoutCacheKey, RunAdvanceDescription*> mCache;
+
+ uint32_t mSize;
+ uint32_t mMaxSize;
+
+ uint32_t mCacheHitCount;
+ uint64_t mNanosecondsSaved;
+
+ uint64_t mCacheStartTime;
+
+ RtlDebugLevel mDebugLevel;
+ bool mDebugEnabled;
+
+ /*
+ * Class initialization
+ */
+ void init();
+
+ /**
+ * Remove oldest entries until we are having enough space
+ */
+ void removeOldests();
+
+ /**
+ * Dump Cache statistics
+ */
+ void dumpCacheStats();
+}; // TextLayoutCache
+
+} // namespace android
+#endif /* ANDROID_TEXT_LAYOUT_CACHE_H */
+