PathCache.cpp revision b933055cf3f7f8ea89bfd3bc9c37a3891ff7310a
1/* 2 * Copyright (C) 2013 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 "OpenGLRenderer" 18#define ATRACE_TAG ATRACE_TAG_VIEW 19 20#include <SkBitmap.h> 21#include <SkCanvas.h> 22#include <SkPaint.h> 23#include <SkPath.h> 24#include <SkRect.h> 25 26#include <utils/JenkinsHash.h> 27#include <utils/Trace.h> 28 29#include "Caches.h" 30#include "PathCache.h" 31 32#include "thread/Signal.h" 33#include "thread/Task.h" 34#include "thread/TaskProcessor.h" 35 36namespace android { 37namespace uirenderer { 38 39/////////////////////////////////////////////////////////////////////////////// 40// Cache entries 41/////////////////////////////////////////////////////////////////////////////// 42 43PathDescription::PathDescription(): 44 type(kShapeNone), 45 join(SkPaint::kDefault_Join), 46 cap(SkPaint::kDefault_Cap), 47 style(SkPaint::kFill_Style), 48 miter(4.0f), 49 strokeWidth(1.0f), 50 pathEffect(NULL) { 51 memset(&shape, 0, sizeof(Shape)); 52} 53 54PathDescription::PathDescription(ShapeType type, const SkPaint* paint): 55 type(type), 56 join(paint->getStrokeJoin()), 57 cap(paint->getStrokeCap()), 58 style(paint->getStyle()), 59 miter(paint->getStrokeMiter()), 60 strokeWidth(paint->getStrokeWidth()), 61 pathEffect(paint->getPathEffect()) { 62 memset(&shape, 0, sizeof(Shape)); 63} 64 65hash_t PathDescription::hash() const { 66 uint32_t hash = JenkinsHashMix(0, type); 67 hash = JenkinsHashMix(hash, join); 68 hash = JenkinsHashMix(hash, cap); 69 hash = JenkinsHashMix(hash, style); 70 hash = JenkinsHashMix(hash, android::hash_type(miter)); 71 hash = JenkinsHashMix(hash, android::hash_type(strokeWidth)); 72 hash = JenkinsHashMix(hash, android::hash_type(pathEffect)); 73 hash = JenkinsHashMixBytes(hash, (uint8_t*) &shape, sizeof(Shape)); 74 return JenkinsHashWhiten(hash); 75} 76 77/////////////////////////////////////////////////////////////////////////////// 78// Utilities 79/////////////////////////////////////////////////////////////////////////////// 80 81bool PathCache::canDrawAsConvexPath(SkPath* path, const SkPaint* paint) { 82 // NOTE: This should only be used after PathTessellator handles joins properly 83 return paint->getPathEffect() == NULL && path->getConvexity() == SkPath::kConvex_Convexity; 84} 85 86void PathCache::computePathBounds(const SkPath* path, const SkPaint* paint, 87 float& left, float& top, float& offset, uint32_t& width, uint32_t& height) { 88 const SkRect& bounds = path->getBounds(); 89 PathCache::computeBounds(bounds, paint, left, top, offset, width, height); 90} 91 92void PathCache::computeBounds(const SkRect& bounds, const SkPaint* paint, 93 float& left, float& top, float& offset, uint32_t& width, uint32_t& height) { 94 const float pathWidth = fmax(bounds.width(), 1.0f); 95 const float pathHeight = fmax(bounds.height(), 1.0f); 96 97 left = bounds.fLeft; 98 top = bounds.fTop; 99 100 offset = (int) floorf(fmax(paint->getStrokeWidth(), 1.0f) * 1.5f + 0.5f); 101 102 width = uint32_t(pathWidth + offset * 2.0 + 0.5); 103 height = uint32_t(pathHeight + offset * 2.0 + 0.5); 104} 105 106static void initBitmap(SkBitmap& bitmap, uint32_t width, uint32_t height) { 107 bitmap.allocPixels(SkImageInfo::MakeA8(width, height)); 108 bitmap.eraseColor(0); 109} 110 111static void initPaint(SkPaint& paint) { 112 // Make sure the paint is opaque, color, alpha, filter, etc. 113 // will be applied later when compositing the alpha8 texture 114 paint.setColor(0xff000000); 115 paint.setAlpha(255); 116 paint.setColorFilter(NULL); 117 paint.setMaskFilter(NULL); 118 paint.setShader(NULL); 119 SkXfermode* mode = SkXfermode::Create(SkXfermode::kSrc_Mode); 120 SkSafeUnref(paint.setXfermode(mode)); 121} 122 123static void drawPath(const SkPath *path, const SkPaint* paint, SkBitmap& bitmap, 124 float left, float top, float offset, uint32_t width, uint32_t height) { 125 initBitmap(bitmap, width, height); 126 127 SkPaint pathPaint(*paint); 128 initPaint(pathPaint); 129 130 SkCanvas canvas(bitmap); 131 canvas.translate(-left + offset, -top + offset); 132 canvas.drawPath(*path, pathPaint); 133} 134 135static PathTexture* createTexture(float left, float top, float offset, 136 uint32_t width, uint32_t height, uint32_t id) { 137 PathTexture* texture = new PathTexture(Caches::getInstance()); 138 texture->left = left; 139 texture->top = top; 140 texture->offset = offset; 141 texture->width = width; 142 texture->height = height; 143 texture->generation = id; 144 return texture; 145} 146 147/////////////////////////////////////////////////////////////////////////////// 148// Cache constructor/destructor 149/////////////////////////////////////////////////////////////////////////////// 150 151PathCache::PathCache(): 152 mCache(LruCache<PathDescription, PathTexture*>::kUnlimitedCapacity), 153 mSize(0), mMaxSize(MB(DEFAULT_PATH_CACHE_SIZE)) { 154 char property[PROPERTY_VALUE_MAX]; 155 if (property_get(PROPERTY_PATH_CACHE_SIZE, property, NULL) > 0) { 156 INIT_LOGD(" Setting %s cache size to %sMB", name, property); 157 setMaxSize(MB(atof(property))); 158 } else { 159 INIT_LOGD(" Using default %s cache size of %.2fMB", name, DEFAULT_PATH_CACHE_SIZE); 160 } 161 162 mCache.setOnEntryRemovedListener(this); 163 164 GLint maxTextureSize; 165 glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize); 166 mMaxTextureSize = maxTextureSize; 167 168 mDebugEnabled = readDebugLevel() & kDebugCaches; 169} 170 171PathCache::~PathCache() { 172 mCache.clear(); 173} 174 175/////////////////////////////////////////////////////////////////////////////// 176// Size management 177/////////////////////////////////////////////////////////////////////////////// 178 179uint32_t PathCache::getSize() { 180 return mSize; 181} 182 183uint32_t PathCache::getMaxSize() { 184 return mMaxSize; 185} 186 187void PathCache::setMaxSize(uint32_t maxSize) { 188 mMaxSize = maxSize; 189 while (mSize > mMaxSize) { 190 mCache.removeOldest(); 191 } 192} 193 194/////////////////////////////////////////////////////////////////////////////// 195// Callbacks 196/////////////////////////////////////////////////////////////////////////////// 197 198void PathCache::operator()(PathDescription& entry, PathTexture*& texture) { 199 removeTexture(texture); 200} 201 202/////////////////////////////////////////////////////////////////////////////// 203// Caching 204/////////////////////////////////////////////////////////////////////////////// 205 206void PathCache::removeTexture(PathTexture* texture) { 207 if (texture) { 208 const uint32_t size = texture->width * texture->height; 209 210 // If there is a pending task we must wait for it to return 211 // before attempting our cleanup 212 const sp<Task<SkBitmap*> >& task = texture->task(); 213 if (task != NULL) { 214 SkBitmap* bitmap = task->getResult(); 215 texture->clearTask(); 216 } else { 217 // If there is a pending task, the path was not added 218 // to the cache and the size wasn't increased 219 if (size > mSize) { 220 ALOGE("Removing path texture of size %d will leave " 221 "the cache in an inconsistent state", size); 222 } 223 mSize -= size; 224 } 225 226 PATH_LOGD("PathCache::delete name, size, mSize = %d, %d, %d", 227 texture->id, size, mSize); 228 if (mDebugEnabled) { 229 ALOGD("Shape deleted, size = %d", size); 230 } 231 232 if (texture->id) { 233 Caches::getInstance().deleteTexture(texture->id); 234 } 235 delete texture; 236 } 237} 238 239void PathCache::purgeCache(uint32_t width, uint32_t height) { 240 const uint32_t size = width * height; 241 // Don't even try to cache a bitmap that's bigger than the cache 242 if (size < mMaxSize) { 243 while (mSize + size > mMaxSize) { 244 mCache.removeOldest(); 245 } 246 } 247} 248 249void PathCache::trim() { 250 while (mSize > mMaxSize) { 251 mCache.removeOldest(); 252 } 253} 254 255PathTexture* PathCache::addTexture(const PathDescription& entry, const SkPath *path, 256 const SkPaint* paint) { 257 ATRACE_CALL(); 258 259 float left, top, offset; 260 uint32_t width, height; 261 computePathBounds(path, paint, left, top, offset, width, height); 262 263 if (!checkTextureSize(width, height)) return NULL; 264 265 purgeCache(width, height); 266 267 SkBitmap bitmap; 268 drawPath(path, paint, bitmap, left, top, offset, width, height); 269 270 PathTexture* texture = createTexture(left, top, offset, width, height, 271 path->getGenerationID()); 272 generateTexture(entry, &bitmap, texture); 273 274 return texture; 275} 276 277void PathCache::generateTexture(const PathDescription& entry, SkBitmap* bitmap, 278 PathTexture* texture, bool addToCache) { 279 generateTexture(*bitmap, texture); 280 281 uint32_t size = texture->width * texture->height; 282 if (size < mMaxSize) { 283 mSize += size; 284 PATH_LOGD("PathCache::get/create: name, size, mSize = %d, %d, %d", 285 texture->id, size, mSize); 286 if (mDebugEnabled) { 287 ALOGD("Shape created, size = %d", size); 288 } 289 if (addToCache) { 290 mCache.put(entry, texture); 291 } 292 } else { 293 // It's okay to add a texture that's bigger than the cache since 294 // we'll trim the cache later when addToCache is set to false 295 if (!addToCache) { 296 mSize += size; 297 } 298 texture->cleanup = true; 299 } 300} 301 302void PathCache::clear() { 303 mCache.clear(); 304} 305 306void PathCache::generateTexture(SkBitmap& bitmap, Texture* texture) { 307 SkAutoLockPixels alp(bitmap); 308 if (!bitmap.readyToDraw()) { 309 ALOGE("Cannot generate texture from bitmap"); 310 return; 311 } 312 313 glGenTextures(1, &texture->id); 314 315 Caches::getInstance().bindTexture(texture->id); 316 // Textures are Alpha8 317 glPixelStorei(GL_UNPACK_ALIGNMENT, 1); 318 319 texture->blend = true; 320 glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, texture->width, texture->height, 0, 321 GL_ALPHA, GL_UNSIGNED_BYTE, bitmap.getPixels()); 322 323 texture->setFilter(GL_LINEAR); 324 texture->setWrap(GL_CLAMP_TO_EDGE); 325} 326 327/////////////////////////////////////////////////////////////////////////////// 328// Path precaching 329/////////////////////////////////////////////////////////////////////////////// 330 331PathCache::PathProcessor::PathProcessor(Caches& caches): 332 TaskProcessor<SkBitmap*>(&caches.tasks), mMaxTextureSize(caches.maxTextureSize) { 333} 334 335void PathCache::PathProcessor::onProcess(const sp<Task<SkBitmap*> >& task) { 336 PathTask* t = static_cast<PathTask*>(task.get()); 337 ATRACE_NAME("pathPrecache"); 338 339 float left, top, offset; 340 uint32_t width, height; 341 PathCache::computePathBounds(t->path, &t->paint, left, top, offset, width, height); 342 343 PathTexture* texture = t->texture; 344 texture->left = left; 345 texture->top = top; 346 texture->offset = offset; 347 texture->width = width; 348 texture->height = height; 349 350 if (width <= mMaxTextureSize && height <= mMaxTextureSize) { 351 SkBitmap* bitmap = new SkBitmap(); 352 drawPath(t->path, &t->paint, *bitmap, left, top, offset, width, height); 353 t->setResult(bitmap); 354 } else { 355 texture->width = 0; 356 texture->height = 0; 357 t->setResult(NULL); 358 } 359} 360 361/////////////////////////////////////////////////////////////////////////////// 362// Paths 363/////////////////////////////////////////////////////////////////////////////// 364 365void PathCache::remove(Vector<PathDescription>& pathsToRemove, const path_pair_t& pair) { 366 LruCache<PathDescription, PathTexture*>::Iterator i(mCache); 367 368 while (i.next()) { 369 const PathDescription& key = i.key(); 370 if (key.type == kShapePath && 371 (key.shape.path.mPath == pair.getFirst() || 372 key.shape.path.mPath == pair.getSecond())) { 373 pathsToRemove.push(key); 374 } 375 } 376} 377 378void PathCache::removeDeferred(SkPath* path) { 379 Mutex::Autolock l(mLock); 380 mGarbage.push(path_pair_t(path, const_cast<SkPath*>(path->getSourcePath()))); 381} 382 383void PathCache::clearGarbage() { 384 Vector<PathDescription> pathsToRemove; 385 386 { // scope for the mutex 387 Mutex::Autolock l(mLock); 388 size_t count = mGarbage.size(); 389 for (size_t i = 0; i < count; i++) { 390 const path_pair_t& pair = mGarbage.itemAt(i); 391 remove(pathsToRemove, pair); 392 delete pair.getFirst(); 393 } 394 mGarbage.clear(); 395 } 396 397 for (size_t i = 0; i < pathsToRemove.size(); i++) { 398 mCache.remove(pathsToRemove.itemAt(i)); 399 } 400} 401 402/** 403 * To properly handle path mutations at draw time we always make a copy 404 * of paths objects when recording display lists. The source path points 405 * to the path we originally copied the path from. This ensures we use 406 * the original path as a cache key the first time a path is inserted 407 * in the cache. The source path is also used to reclaim garbage when a 408 * Dalvik Path object is collected. 409 */ 410static const SkPath* getSourcePath(const SkPath* path) { 411 const SkPath* sourcePath = path->getSourcePath(); 412 if (sourcePath && sourcePath->getGenerationID() == path->getGenerationID()) { 413 return const_cast<SkPath*>(sourcePath); 414 } 415 return path; 416} 417 418PathTexture* PathCache::get(const SkPath* path, const SkPaint* paint) { 419 path = getSourcePath(path); 420 421 PathDescription entry(kShapePath, paint); 422 entry.shape.path.mPath = path; 423 424 PathTexture* texture = mCache.get(entry); 425 426 if (!texture) { 427 texture = addTexture(entry, path, paint); 428 } else { 429 // A bitmap is attached to the texture, this means we need to 430 // upload it as a GL texture 431 const sp<Task<SkBitmap*> >& task = texture->task(); 432 if (task != NULL) { 433 // But we must first wait for the worker thread to be done 434 // producing the bitmap, so let's wait 435 SkBitmap* bitmap = task->getResult(); 436 if (bitmap) { 437 generateTexture(entry, bitmap, texture, false); 438 texture->clearTask(); 439 } else { 440 ALOGW("Path too large to be rendered into a texture"); 441 texture->clearTask(); 442 texture = NULL; 443 mCache.remove(entry); 444 } 445 } else if (path->getGenerationID() != texture->generation) { 446 // The size of the path might have changed so we first 447 // remove the entry from the cache 448 mCache.remove(entry); 449 texture = addTexture(entry, path, paint); 450 } 451 } 452 453 return texture; 454} 455 456void PathCache::precache(const SkPath* path, const SkPaint* paint) { 457 if (!Caches::getInstance().tasks.canRunTasks()) { 458 return; 459 } 460 461 path = getSourcePath(path); 462 463 PathDescription entry(kShapePath, paint); 464 entry.shape.path.mPath = path; 465 466 PathTexture* texture = mCache.get(entry); 467 468 bool generate = false; 469 if (!texture) { 470 generate = true; 471 } else if (path->getGenerationID() != texture->generation) { 472 mCache.remove(entry); 473 generate = true; 474 } 475 476 if (generate) { 477 // It is important to specify the generation ID so we do not 478 // attempt to precache the same path several times 479 texture = createTexture(0.0f, 0.0f, 0.0f, 0, 0, path->getGenerationID()); 480 sp<PathTask> task = new PathTask(path, paint, texture); 481 texture->setTask(task); 482 483 // During the precaching phase we insert path texture objects into 484 // the cache that do not point to any GL texture. They are instead 485 // treated as a task for the precaching worker thread. This is why 486 // we do not check the cache limit when inserting these objects. 487 // The conversion into GL texture will happen in get(), when a client 488 // asks for a path texture. This is also when the cache limit will 489 // be enforced. 490 mCache.put(entry, texture); 491 492 if (mProcessor == NULL) { 493 mProcessor = new PathProcessor(Caches::getInstance()); 494 } 495 mProcessor->add(task); 496 } 497} 498 499/////////////////////////////////////////////////////////////////////////////// 500// Rounded rects 501/////////////////////////////////////////////////////////////////////////////// 502 503PathTexture* PathCache::getRoundRect(float width, float height, 504 float rx, float ry, const SkPaint* paint) { 505 PathDescription entry(kShapeRoundRect, paint); 506 entry.shape.roundRect.mWidth = width; 507 entry.shape.roundRect.mHeight = height; 508 entry.shape.roundRect.mRx = rx; 509 entry.shape.roundRect.mRy = ry; 510 511 PathTexture* texture = get(entry); 512 513 if (!texture) { 514 SkPath path; 515 SkRect r; 516 r.set(0.0f, 0.0f, width, height); 517 path.addRoundRect(r, rx, ry, SkPath::kCW_Direction); 518 519 texture = addTexture(entry, &path, paint); 520 } 521 522 return texture; 523} 524 525/////////////////////////////////////////////////////////////////////////////// 526// Circles 527/////////////////////////////////////////////////////////////////////////////// 528 529PathTexture* PathCache::getCircle(float radius, const SkPaint* paint) { 530 PathDescription entry(kShapeCircle, paint); 531 entry.shape.circle.mRadius = radius; 532 533 PathTexture* texture = get(entry); 534 535 if (!texture) { 536 SkPath path; 537 path.addCircle(radius, radius, radius, SkPath::kCW_Direction); 538 539 texture = addTexture(entry, &path, paint); 540 } 541 542 return texture; 543} 544 545/////////////////////////////////////////////////////////////////////////////// 546// Ovals 547/////////////////////////////////////////////////////////////////////////////// 548 549PathTexture* PathCache::getOval(float width, float height, const SkPaint* paint) { 550 PathDescription entry(kShapeOval, paint); 551 entry.shape.oval.mWidth = width; 552 entry.shape.oval.mHeight = height; 553 554 PathTexture* texture = get(entry); 555 556 if (!texture) { 557 SkPath path; 558 SkRect r; 559 r.set(0.0f, 0.0f, width, height); 560 path.addOval(r, SkPath::kCW_Direction); 561 562 texture = addTexture(entry, &path, paint); 563 } 564 565 return texture; 566} 567 568/////////////////////////////////////////////////////////////////////////////// 569// Rects 570/////////////////////////////////////////////////////////////////////////////// 571 572PathTexture* PathCache::getRect(float width, float height, const SkPaint* paint) { 573 PathDescription entry(kShapeRect, paint); 574 entry.shape.rect.mWidth = width; 575 entry.shape.rect.mHeight = height; 576 577 PathTexture* texture = get(entry); 578 579 if (!texture) { 580 SkPath path; 581 SkRect r; 582 r.set(0.0f, 0.0f, width, height); 583 path.addRect(r, SkPath::kCW_Direction); 584 585 texture = addTexture(entry, &path, paint); 586 } 587 588 return texture; 589} 590 591/////////////////////////////////////////////////////////////////////////////// 592// Arcs 593/////////////////////////////////////////////////////////////////////////////// 594 595PathTexture* PathCache::getArc(float width, float height, 596 float startAngle, float sweepAngle, bool useCenter, const SkPaint* paint) { 597 PathDescription entry(kShapeArc, paint); 598 entry.shape.arc.mWidth = width; 599 entry.shape.arc.mHeight = height; 600 entry.shape.arc.mStartAngle = startAngle; 601 entry.shape.arc.mSweepAngle = sweepAngle; 602 entry.shape.arc.mUseCenter = useCenter; 603 604 PathTexture* texture = get(entry); 605 606 if (!texture) { 607 SkPath path; 608 SkRect r; 609 r.set(0.0f, 0.0f, width, height); 610 if (useCenter) { 611 path.moveTo(r.centerX(), r.centerY()); 612 } 613 path.arcTo(r, startAngle, sweepAngle, !useCenter); 614 if (useCenter) { 615 path.close(); 616 } 617 618 texture = addTexture(entry, &path, paint); 619 } 620 621 return texture; 622} 623 624}; // namespace uirenderer 625}; // namespace android 626