TextLayoutCache.cpp revision 5c863f741e8e484bb39decd516c9fa4c6322e671
1/* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17#define LOG_TAG "TextLayoutCache" 18 19#include "TextLayoutCache.h" 20#include "TextLayout.h" 21 22extern "C" { 23 #include "harfbuzz-unicode.h" 24} 25 26namespace android { 27 28//-------------------------------------------------------------------------------------------------- 29#if USE_TEXT_LAYOUT_CACHE 30 ANDROID_SINGLETON_STATIC_INSTANCE(TextLayoutCache); 31#endif 32//-------------------------------------------------------------------------------------------------- 33 34TextLayoutCache::TextLayoutCache() : 35 mCache(GenerationCache<TextLayoutCacheKey, sp<TextLayoutCacheValue> >::kUnlimitedCapacity), 36 mSize(0), mMaxSize(MB(DEFAULT_TEXT_LAYOUT_CACHE_SIZE_IN_MB)), 37 mCacheHitCount(0), mNanosecondsSaved(0) { 38 init(); 39} 40 41TextLayoutCache::~TextLayoutCache() { 42 mCache.clear(); 43} 44 45void TextLayoutCache::init() { 46 mCache.setOnEntryRemovedListener(this); 47 48 mDebugLevel = readRtlDebugLevel(); 49 mDebugEnabled = mDebugLevel & kRtlDebugCaches; 50 LOGD("Using debug level: %d - Debug Enabled: %d", mDebugLevel, mDebugEnabled); 51 52 mCacheStartTime = systemTime(SYSTEM_TIME_MONOTONIC); 53 54 if (mDebugEnabled) { 55 LOGD("Initialization is done - Start time: %lld", mCacheStartTime); 56 } 57 58 mInitialized = true; 59} 60 61/* 62 * Size management 63 */ 64 65uint32_t TextLayoutCache::getSize() { 66 return mSize; 67} 68 69uint32_t TextLayoutCache::getMaxSize() { 70 return mMaxSize; 71} 72 73void TextLayoutCache::setMaxSize(uint32_t maxSize) { 74 mMaxSize = maxSize; 75 removeOldests(); 76} 77 78void TextLayoutCache::removeOldests() { 79 while (mSize > mMaxSize) { 80 mCache.removeOldest(); 81 } 82} 83 84/** 85 * Callbacks 86 */ 87void TextLayoutCache::operator()(TextLayoutCacheKey& text, sp<TextLayoutCacheValue>& desc) { 88 if (desc != NULL) { 89 size_t totalSizeToDelete = text.getSize() + desc->getSize(); 90 mSize -= totalSizeToDelete; 91 if (mDebugEnabled) { 92 LOGD("Cache value deleted, size = %d", totalSizeToDelete); 93 } 94 desc.clear(); 95 } 96} 97 98/* 99 * Cache clearing 100 */ 101void TextLayoutCache::clear() { 102 mCache.clear(); 103} 104 105/* 106 * Caching 107 */ 108sp<TextLayoutCacheValue> TextLayoutCache::getValue(SkPaint* paint, 109 const jchar* text, jint start, jint count, jint contextCount, jint dirFlags) { 110 AutoMutex _l(mLock); 111 nsecs_t startTime = 0; 112 if (mDebugEnabled) { 113 startTime = systemTime(SYSTEM_TIME_MONOTONIC); 114 } 115 116 // Create the key 117 TextLayoutCacheKey key(paint, text, start, count, contextCount, dirFlags); 118 119 // Get value from cache if possible 120 sp<TextLayoutCacheValue> value = mCache.get(key); 121 122 // Value not found for the key, we need to add a new value in the cache 123 if (value == NULL) { 124 if (mDebugEnabled) { 125 startTime = systemTime(SYSTEM_TIME_MONOTONIC); 126 } 127 128 value = new TextLayoutCacheValue(); 129 130 // Compute advances and store them 131 value->computeValues(paint, text, start, count, contextCount, dirFlags); 132 133 nsecs_t endTime = systemTime(SYSTEM_TIME_MONOTONIC); 134 135 // Don't bother to add in the cache if the entry is too big 136 size_t size = key.getSize() + value->getSize(); 137 if (size <= mMaxSize) { 138 // Cleanup to make some room if needed 139 if (mSize + size > mMaxSize) { 140 if (mDebugEnabled) { 141 LOGD("Need to clean some entries for making some room for a new entry"); 142 } 143 while (mSize + size > mMaxSize) { 144 // This will call the callback 145 mCache.removeOldest(); 146 } 147 } 148 149 // Update current cache size 150 mSize += size; 151 152 // Copy the text when we insert the new entry 153 key.internalTextCopy(); 154 mCache.put(key, value); 155 156 if (mDebugEnabled) { 157 // Update timing information for statistics 158 value->setElapsedTime(endTime - startTime); 159 160 LOGD("CACHE MISS: Added entry with " 161 "count=%d, entry size %d bytes, remaining space %d bytes" 162 " - Compute time in nanos: %d - Text='%s' ", 163 count, size, mMaxSize - mSize, value->getElapsedTime(), 164 String8(text, count).string()); 165 } 166 } else { 167 if (mDebugEnabled) { 168 LOGD("CACHE MISS: Calculated but not storing entry because it is too big " 169 "with start=%d count=%d contextCount=%d, " 170 "entry size %d bytes, remaining space %d bytes" 171 " - Compute time in nanos: %lld - Text='%s'", 172 start, count, contextCount, size, mMaxSize - mSize, endTime, 173 String8(text, count).string()); 174 } 175 value.clear(); 176 } 177 } else { 178 // This is a cache hit, just log timestamp and user infos 179 if (mDebugEnabled) { 180 nsecs_t elapsedTimeThruCacheGet = systemTime(SYSTEM_TIME_MONOTONIC) - startTime; 181 mNanosecondsSaved += (value->getElapsedTime() - elapsedTimeThruCacheGet); 182 ++mCacheHitCount; 183 184 if (value->getElapsedTime() > 0) { 185 float deltaPercent = 100 * ((value->getElapsedTime() - elapsedTimeThruCacheGet) 186 / ((float)value->getElapsedTime())); 187 LOGD("CACHE HIT #%d with start=%d count=%d contextCount=%d" 188 "- Compute time in nanos: %d - " 189 "Cache get time in nanos: %lld - Gain in percent: %2.2f - Text='%s' ", 190 mCacheHitCount, start, count, contextCount, 191 value->getElapsedTime(), elapsedTimeThruCacheGet, deltaPercent, 192 String8(text, count).string()); 193 } 194 if (mCacheHitCount % DEFAULT_DUMP_STATS_CACHE_HIT_INTERVAL == 0) { 195 dumpCacheStats(); 196 } 197 } 198 } 199 return value; 200} 201 202void TextLayoutCache::dumpCacheStats() { 203 float remainingPercent = 100 * ((mMaxSize - mSize) / ((float)mMaxSize)); 204 float timeRunningInSec = (systemTime(SYSTEM_TIME_MONOTONIC) - mCacheStartTime) / 1000000000; 205 LOGD("------------------------------------------------"); 206 LOGD("Cache stats"); 207 LOGD("------------------------------------------------"); 208 LOGD("pid : %d", getpid()); 209 LOGD("running : %.0f seconds", timeRunningInSec); 210 LOGD("entries : %d", mCache.size()); 211 LOGD("size : %d bytes", mMaxSize); 212 LOGD("remaining : %d bytes or %2.2f percent", mMaxSize - mSize, remainingPercent); 213 LOGD("hits : %d", mCacheHitCount); 214 LOGD("saved : %lld milliseconds", mNanosecondsSaved / 1000000); 215 LOGD("------------------------------------------------"); 216} 217 218/** 219 * TextLayoutCacheKey 220 */ 221TextLayoutCacheKey::TextLayoutCacheKey(): text(NULL), start(0), count(0), contextCount(0), 222 dirFlags(0), typeface(NULL), textSize(0), textSkewX(0), textScaleX(0), flags(0), 223 hinting(SkPaint::kNo_Hinting) { 224} 225 226TextLayoutCacheKey::TextLayoutCacheKey(const SkPaint* paint, const UChar* text, 227 size_t start, size_t count, size_t contextCount, int dirFlags) : 228 text(text), start(start), count(count), contextCount(contextCount), 229 dirFlags(dirFlags) { 230 typeface = paint->getTypeface(); 231 textSize = paint->getTextSize(); 232 textSkewX = paint->getTextSkewX(); 233 textScaleX = paint->getTextScaleX(); 234 flags = paint->getFlags(); 235 hinting = paint->getHinting(); 236} 237 238TextLayoutCacheKey::TextLayoutCacheKey(const TextLayoutCacheKey& other) : 239 text(NULL), 240 textCopy(other.textCopy), 241 start(other.start), 242 count(other.count), 243 contextCount(other.contextCount), 244 dirFlags(other.dirFlags), 245 typeface(other.typeface), 246 textSize(other.textSize), 247 textSkewX(other.textSkewX), 248 textScaleX(other.textScaleX), 249 flags(other.flags), 250 hinting(other.hinting) { 251 if (other.text) { 252 textCopy.setTo(other.text); 253 } 254} 255 256int TextLayoutCacheKey::compare(const TextLayoutCacheKey& lhs, const TextLayoutCacheKey& rhs) { 257 int deltaInt = lhs.start - rhs.start; 258 if (deltaInt != 0) return (deltaInt); 259 260 deltaInt = lhs.count - rhs.count; 261 if (deltaInt != 0) return (deltaInt); 262 263 deltaInt = lhs.contextCount - rhs.contextCount; 264 if (deltaInt != 0) return (deltaInt); 265 266 if (lhs.typeface < rhs.typeface) return -1; 267 if (lhs.typeface > rhs.typeface) return +1; 268 269 if (lhs.textSize < rhs.textSize) return -1; 270 if (lhs.textSize > rhs.textSize) return +1; 271 272 if (lhs.textSkewX < rhs.textSkewX) return -1; 273 if (lhs.textSkewX > rhs.textSkewX) return +1; 274 275 if (lhs.textScaleX < rhs.textScaleX) return -1; 276 if (lhs.textScaleX > rhs.textScaleX) return +1; 277 278 deltaInt = lhs.flags - rhs.flags; 279 if (deltaInt != 0) return (deltaInt); 280 281 deltaInt = lhs.hinting - rhs.hinting; 282 if (deltaInt != 0) return (deltaInt); 283 284 deltaInt = lhs.dirFlags - rhs.dirFlags; 285 if (deltaInt) return (deltaInt); 286 287 return memcmp(lhs.getText(), rhs.getText(), lhs.contextCount * sizeof(UChar)); 288} 289 290void TextLayoutCacheKey::internalTextCopy() { 291 textCopy.setTo(text, contextCount); 292 text = NULL; 293} 294 295size_t TextLayoutCacheKey::getSize() { 296 return sizeof(TextLayoutCacheKey) + sizeof(UChar) * contextCount; 297} 298 299/** 300 * TextLayoutCacheValue 301 */ 302TextLayoutCacheValue::TextLayoutCacheValue() : 303 mTotalAdvance(0), mElapsedTime(0) { 304} 305 306void TextLayoutCacheValue::setElapsedTime(uint32_t time) { 307 mElapsedTime = time; 308} 309 310uint32_t TextLayoutCacheValue::getElapsedTime() { 311 return mElapsedTime; 312} 313 314void TextLayoutCacheValue::computeValues(SkPaint* paint, const UChar* chars, 315 size_t start, size_t count, size_t contextCount, int dirFlags) { 316 // Give a hint for advances, glyphs and log clusters vectors size 317 mAdvances.setCapacity(contextCount); 318 mGlyphs.setCapacity(contextCount); 319 320 computeValuesWithHarfbuzz(paint, chars, start, count, contextCount, dirFlags, 321 &mAdvances, &mTotalAdvance, &mGlyphs); 322#if DEBUG_ADVANCES 323 LOGD("Advances - start=%d, count=%d, countextCount=%d, totalAdvance=%f", start, count, 324 contextCount, mTotalAdvance); 325#endif 326} 327 328size_t TextLayoutCacheValue::getSize() { 329 return sizeof(TextLayoutCacheValue) + sizeof(jfloat) * mAdvances.capacity() + 330 sizeof(jchar) * mGlyphs.capacity(); 331} 332 333void TextLayoutCacheValue::initShaperItem(HB_ShaperItem& shaperItem, HB_FontRec* font, 334 FontData* fontData, SkPaint* paint, const UChar* chars, size_t contextCount) { 335 // Zero the Shaper struct 336 memset(&shaperItem, 0, sizeof(shaperItem)); 337 338 font->klass = &harfbuzzSkiaClass; 339 font->userData = 0; 340 341 // The values which harfbuzzSkiaClass returns are already scaled to 342 // pixel units, so we just set all these to one to disable further 343 // scaling. 344 font->x_ppem = 1; 345 font->y_ppem = 1; 346 font->x_scale = 1; 347 font->y_scale = 1; 348 349 shaperItem.font = font; 350 shaperItem.face = HB_NewFace(shaperItem.font, harfbuzzSkiaGetTable); 351 352 // Reset kerning 353 shaperItem.kerning_applied = false; 354 355 // Define font data 356 fontData->typeFace = paint->getTypeface(); 357 fontData->textSize = paint->getTextSize(); 358 fontData->textSkewX = paint->getTextSkewX(); 359 fontData->textScaleX = paint->getTextScaleX(); 360 fontData->flags = paint->getFlags(); 361 fontData->hinting = paint->getHinting(); 362 363 shaperItem.font->userData = fontData; 364 365 // We cannot know, ahead of time, how many glyphs a given script run 366 // will produce. We take a guess that script runs will not produce more 367 // than twice as many glyphs as there are code points plus a bit of 368 // padding and fallback if we find that we are wrong. 369 createGlyphArrays(shaperItem, (contextCount + 2) * 2); 370 371 // Create log clusters array 372 shaperItem.log_clusters = new unsigned short[contextCount]; 373 374 // Set the string properties 375 shaperItem.string = chars; 376 shaperItem.stringLength = contextCount; 377} 378 379void TextLayoutCacheValue::freeShaperItem(HB_ShaperItem& shaperItem) { 380 deleteGlyphArrays(shaperItem); 381 delete[] shaperItem.log_clusters; 382 HB_FreeFace(shaperItem.face); 383} 384 385void TextLayoutCacheValue::shapeRun(HB_ShaperItem& shaperItem, size_t start, size_t count, 386 bool isRTL) { 387 // Update Harfbuzz Shaper 388 shaperItem.item.pos = start; 389 shaperItem.item.length = count; 390 shaperItem.item.bidiLevel = isRTL; 391 392 shaperItem.item.script = isRTL ? HB_Script_Arabic : HB_Script_Common; 393 394 // Shape 395 while (!HB_ShapeItem(&shaperItem)) { 396 // We overflowed our arrays. Resize and retry. 397 // HB_ShapeItem fills in shaperItem.num_glyphs with the needed size. 398 deleteGlyphArrays(shaperItem); 399 createGlyphArrays(shaperItem, shaperItem.num_glyphs << 1); 400 } 401} 402 403void TextLayoutCacheValue::computeValuesWithHarfbuzz(SkPaint* paint, const UChar* chars, 404 size_t start, size_t count, size_t contextCount, int dirFlags, 405 Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance, 406 Vector<jchar>* const outGlyphs) { 407 408 UBiDiLevel bidiReq = 0; 409 bool forceLTR = false; 410 bool forceRTL = false; 411 412 switch (dirFlags) { 413 case kBidi_LTR: bidiReq = 0; break; // no ICU constant, canonical LTR level 414 case kBidi_RTL: bidiReq = 1; break; // no ICU constant, canonical RTL level 415 case kBidi_Default_LTR: bidiReq = UBIDI_DEFAULT_LTR; break; 416 case kBidi_Default_RTL: bidiReq = UBIDI_DEFAULT_RTL; break; 417 case kBidi_Force_LTR: forceLTR = true; break; // every char is LTR 418 case kBidi_Force_RTL: forceRTL = true; break; // every char is RTL 419 } 420 421 HB_ShaperItem shaperItem; 422 HB_FontRec font; 423 FontData fontData; 424 425 // Initialize Harfbuzz Shaper 426 initShaperItem(shaperItem, &font, &fontData, paint, chars, contextCount); 427 428 if (forceLTR || forceRTL) { 429#if DEBUG_GLYPHS 430 LOGD("computeValuesWithHarfbuzz -- forcing run with LTR=%d RTL=%d", 431 forceLTR, forceRTL); 432#endif 433 computeRunValuesWithHarfbuzz(shaperItem, paint, 434 start, count, forceRTL, 435 outAdvances, outTotalAdvance, outGlyphs); 436 } else { 437 UBiDi* bidi = ubidi_open(); 438 if (bidi) { 439 UErrorCode status = U_ZERO_ERROR; 440#if DEBUG_GLYPHS 441 LOGD("computeValuesWithHarfbuzz -- bidiReq=%d", bidiReq); 442#endif 443 ubidi_setPara(bidi, chars, contextCount, bidiReq, NULL, &status); 444 if (U_SUCCESS(status)) { 445 int paraDir = ubidi_getParaLevel(bidi) & kDirection_Mask; // 0 if ltr, 1 if rtl 446 size_t rc = ubidi_countRuns(bidi, &status); 447#if DEBUG_GLYPHS 448 LOGD("computeValuesWithHarfbuzz -- dirFlags=%d run-count=%d paraDir=%d", 449 dirFlags, rc, paraDir); 450#endif 451 if (rc == 1 || !U_SUCCESS(status)) { 452 bool isRTL = (paraDir == 1); 453#if DEBUG_GLYPHS 454 LOGD("computeValuesWithHarfbuzz -- processing SINGLE run " 455 "-- run-start=%d run-len=%d isRTL=%d", start, count, isRTL); 456#endif 457 computeRunValuesWithHarfbuzz(shaperItem, paint, 458 start, count, isRTL, 459 outAdvances, outTotalAdvance, outGlyphs); 460 } else { 461 int32_t end = start + count; 462 for (size_t i = 0; i < rc; ++i) { 463 int32_t startRun; 464 int32_t lengthRun; 465 UBiDiDirection runDir = ubidi_getVisualRun(bidi, i, &startRun, &lengthRun); 466 467 if (startRun >= end) { 468 continue; 469 } 470 int32_t endRun = startRun + lengthRun; 471 if (endRun <= start) { 472 continue; 473 } 474 if (startRun < start) { 475 startRun = start; 476 } 477 if (endRun > end) { 478 endRun = end; 479 } 480 481 lengthRun = endRun - startRun; 482 bool isRTL = (runDir == UBIDI_RTL); 483 jfloat runTotalAdvance = 0; 484#if DEBUG_GLYPHS 485 LOGD("computeValuesWithHarfbuzz -- run-start=%d run-len=%d isRTL=%d", 486 startRun, lengthRun, isRTL); 487#endif 488 computeRunValuesWithHarfbuzz(shaperItem, paint, 489 startRun, lengthRun, isRTL, 490 outAdvances, &runTotalAdvance, outGlyphs); 491 492 *outTotalAdvance += runTotalAdvance; 493 } 494 } 495 } 496 ubidi_close(bidi); 497 } else { 498 // Cannot run BiDi, just consider one Run 499 bool isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL); 500#if DEBUG_GLYPHS 501 LOGD("computeValuesWithHarfbuzz -- cannot run BiDi, considering a SINGLE Run " 502 "-- run-start=%d run-len=%d isRTL=%d", start, count, isRTL); 503#endif 504 computeRunValuesWithHarfbuzz(shaperItem, paint, 505 start, count, isRTL, 506 outAdvances, outTotalAdvance, outGlyphs); 507 } 508 } 509 510 // Cleaning 511 freeShaperItem(shaperItem); 512 513#if DEBUG_GLYPHS 514 LOGD("computeValuesWithHarfbuzz -- total-glyphs-count=%d", outGlyphs->size()); 515#endif 516} 517 518static void logGlyphs(HB_ShaperItem shaperItem) { 519 LOGD("Got glyphs - count=%d", shaperItem.num_glyphs); 520 for (size_t i = 0; i < shaperItem.num_glyphs; i++) { 521 LOGD(" glyph[%d]=%d - offset.x=%f offset.y=%f", i, shaperItem.glyphs[i], 522 HBFixedToFloat(shaperItem.offsets[i].x), 523 HBFixedToFloat(shaperItem.offsets[i].y)); 524 } 525} 526 527void TextLayoutCacheValue::computeRunValuesWithHarfbuzz(HB_ShaperItem& shaperItem, SkPaint* paint, 528 size_t start, size_t count, bool isRTL, 529 Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance, 530 Vector<jchar>* const outGlyphs) { 531 532 shapeRun(shaperItem, start, count, isRTL); 533 534#if DEBUG_GLYPHS 535 LOGD("HARFBUZZ -- num_glypth=%d - kerning_applied=%d", shaperItem.num_glyphs, 536 shaperItem.kerning_applied); 537 LOGD(" -- string= '%s'", String8(chars + start, count).string()); 538 LOGD(" -- isDevKernText=%d", paint->isDevKernText()); 539 540 logGlyphs(shaperItem); 541#endif 542 543 if (shaperItem.advances == NULL || shaperItem.num_glyphs == 0) { 544#if DEBUG_GLYPHS 545 LOGD("HARFBUZZ -- advances array is empty or num_glypth = 0"); 546#endif 547 outAdvances->insertAt(0, outAdvances->size(), count); 548 *outTotalAdvance = 0; 549 return; 550 } 551 552 // Get Advances and their total 553 jfloat currentAdvance = HBFixedToFloat(shaperItem.advances[shaperItem.log_clusters[0]]); 554 jfloat totalAdvance = currentAdvance; 555 outAdvances->add(currentAdvance); 556 for (size_t i = 1; i < count; i++) { 557 size_t clusterPrevious = shaperItem.log_clusters[i - 1]; 558 size_t cluster = shaperItem.log_clusters[i]; 559 if (cluster == clusterPrevious) { 560 outAdvances->add(0); 561 } else { 562 currentAdvance = HBFixedToFloat(shaperItem.advances[shaperItem.log_clusters[i]]); 563 totalAdvance += currentAdvance; 564 outAdvances->add(currentAdvance); 565 } 566 } 567 *outTotalAdvance = totalAdvance; 568 569#if DEBUG_ADVANCES 570 for (size_t i = 0; i < count; i++) { 571 LOGD("hb-adv[%d] = %f - log_clusters = %d - total = %f", i, 572 (*outAdvances)[i], shaperItem.log_clusters[i], totalAdvance); 573 } 574#endif 575 576 // Get Glyphs and reverse them in place if RTL 577 if (outGlyphs) { 578 size_t countGlyphs = shaperItem.num_glyphs; 579 for (size_t i = 0; i < countGlyphs; i++) { 580 jchar glyph = (jchar) shaperItem.glyphs[(!isRTL) ? i : countGlyphs - 1 - i]; 581#if DEBUG_GLYPHS 582 LOGD("HARFBUZZ -- glyph[%d]=%d", i, glyph); 583#endif 584 outGlyphs->add(glyph); 585 } 586 } 587} 588 589void TextLayoutCacheValue::deleteGlyphArrays(HB_ShaperItem& shaperItem) { 590 delete[] shaperItem.glyphs; 591 delete[] shaperItem.attributes; 592 delete[] shaperItem.advances; 593 delete[] shaperItem.offsets; 594} 595 596void TextLayoutCacheValue::createGlyphArrays(HB_ShaperItem& shaperItem, int size) { 597 shaperItem.glyphs = new HB_Glyph[size]; 598 shaperItem.attributes = new HB_GlyphAttributes[size]; 599 shaperItem.advances = new HB_Fixed[size]; 600 shaperItem.offsets = new HB_FixedPoint[size]; 601 shaperItem.num_glyphs = size; 602} 603 604} // namespace android 605