PathCache.cpp revision 2507c34d91bb0d722b6012e85cb47387b2aa6873
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 <SkColor.h> 23#include <SkPaint.h> 24#include <SkPath.h> 25#include <SkRect.h> 26 27#include <utils/JenkinsHash.h> 28#include <utils/Trace.h> 29 30#include "Caches.h" 31#include "PathCache.h" 32 33#include "thread/Signal.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(nullptr) { 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() == nullptr && 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(SK_ColorBLACK); 115 paint.setAlpha(255); 116 paint.setColorFilter(nullptr); 117 paint.setMaskFilter(nullptr); 118 paint.setShader(nullptr); 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 135/////////////////////////////////////////////////////////////////////////////// 136// Cache constructor/destructor 137/////////////////////////////////////////////////////////////////////////////// 138 139PathCache::PathCache(): 140 mCache(LruCache<PathDescription, PathTexture*>::kUnlimitedCapacity), 141 mSize(0), mMaxSize(MB(DEFAULT_PATH_CACHE_SIZE)) { 142 char property[PROPERTY_VALUE_MAX]; 143 if (property_get(PROPERTY_PATH_CACHE_SIZE, property, nullptr) > 0) { 144 INIT_LOGD(" Setting %s cache size to %sMB", name, property); 145 setMaxSize(MB(atof(property))); 146 } else { 147 INIT_LOGD(" Using default %s cache size of %.2fMB", name, DEFAULT_PATH_CACHE_SIZE); 148 } 149 150 mCache.setOnEntryRemovedListener(this); 151 152 GLint maxTextureSize; 153 glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize); 154 mMaxTextureSize = maxTextureSize; 155 156 mDebugEnabled = Properties::debugLevel & kDebugCaches; 157} 158 159PathCache::~PathCache() { 160 mCache.clear(); 161} 162 163/////////////////////////////////////////////////////////////////////////////// 164// Size management 165/////////////////////////////////////////////////////////////////////////////// 166 167uint32_t PathCache::getSize() { 168 return mSize; 169} 170 171uint32_t PathCache::getMaxSize() { 172 return mMaxSize; 173} 174 175void PathCache::setMaxSize(uint32_t maxSize) { 176 mMaxSize = maxSize; 177 while (mSize > mMaxSize) { 178 mCache.removeOldest(); 179 } 180} 181 182/////////////////////////////////////////////////////////////////////////////// 183// Callbacks 184/////////////////////////////////////////////////////////////////////////////// 185 186void PathCache::operator()(PathDescription& entry, PathTexture*& texture) { 187 removeTexture(texture); 188} 189 190/////////////////////////////////////////////////////////////////////////////// 191// Caching 192/////////////////////////////////////////////////////////////////////////////// 193 194void PathCache::removeTexture(PathTexture* texture) { 195 if (texture) { 196 const uint32_t size = texture->width * texture->height; 197 198 // If there is a pending task we must wait for it to return 199 // before attempting our cleanup 200 const sp<Task<SkBitmap*> >& task = texture->task(); 201 if (task != nullptr) { 202 task->getResult(); 203 texture->clearTask(); 204 } else { 205 // If there is a pending task, the path was not added 206 // to the cache and the size wasn't increased 207 if (size > mSize) { 208 ALOGE("Removing path texture of size %d will leave " 209 "the cache in an inconsistent state", size); 210 } 211 mSize -= size; 212 } 213 214 PATH_LOGD("PathCache::delete name, size, mSize = %d, %d, %d", 215 texture->id, size, mSize); 216 if (mDebugEnabled) { 217 ALOGD("Shape deleted, size = %d", size); 218 } 219 220 if (texture->id) { 221 Caches::getInstance().textureState().deleteTexture(texture->id); 222 } 223 delete texture; 224 } 225} 226 227void PathCache::purgeCache(uint32_t width, uint32_t height) { 228 const uint32_t size = width * height; 229 // Don't even try to cache a bitmap that's bigger than the cache 230 if (size < mMaxSize) { 231 while (mSize + size > mMaxSize) { 232 mCache.removeOldest(); 233 } 234 } 235} 236 237void PathCache::trim() { 238 while (mSize > mMaxSize) { 239 mCache.removeOldest(); 240 } 241} 242 243PathTexture* PathCache::addTexture(const PathDescription& entry, const SkPath *path, 244 const SkPaint* paint) { 245 ATRACE_NAME("Generate Path Texture"); 246 247 float left, top, offset; 248 uint32_t width, height; 249 computePathBounds(path, paint, left, top, offset, width, height); 250 251 if (!checkTextureSize(width, height)) return nullptr; 252 253 purgeCache(width, height); 254 255 SkBitmap bitmap; 256 drawPath(path, paint, bitmap, left, top, offset, width, height); 257 258 PathTexture* texture = new PathTexture(Caches::getInstance(), 259 left, top, offset, width, height, 260 path->getGenerationID()); 261 generateTexture(entry, &bitmap, texture); 262 263 return texture; 264} 265 266void PathCache::generateTexture(const PathDescription& entry, SkBitmap* bitmap, 267 PathTexture* texture, bool addToCache) { 268 generateTexture(*bitmap, texture); 269 270 uint32_t size = texture->width * texture->height; 271 if (size < mMaxSize) { 272 mSize += size; 273 PATH_LOGD("PathCache::get/create: name, size, mSize = %d, %d, %d", 274 texture->id, size, mSize); 275 if (mDebugEnabled) { 276 ALOGD("Shape created, size = %d", size); 277 } 278 if (addToCache) { 279 mCache.put(entry, texture); 280 } 281 } else { 282 // It's okay to add a texture that's bigger than the cache since 283 // we'll trim the cache later when addToCache is set to false 284 if (!addToCache) { 285 mSize += size; 286 } 287 texture->cleanup = true; 288 } 289} 290 291void PathCache::clear() { 292 mCache.clear(); 293} 294 295void PathCache::generateTexture(SkBitmap& bitmap, Texture* texture) { 296 SkAutoLockPixels alp(bitmap); 297 if (!bitmap.readyToDraw()) { 298 ALOGE("Cannot generate texture from bitmap"); 299 return; 300 } 301 302 glGenTextures(1, &texture->id); 303 304 Caches::getInstance().textureState().bindTexture(texture->id); 305 // Textures are Alpha8 306 glPixelStorei(GL_UNPACK_ALIGNMENT, 1); 307 308 texture->blend = true; 309 glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, texture->width, texture->height, 0, 310 GL_ALPHA, GL_UNSIGNED_BYTE, bitmap.getPixels()); 311 312 texture->setFilter(GL_LINEAR); 313 texture->setWrap(GL_CLAMP_TO_EDGE); 314} 315 316/////////////////////////////////////////////////////////////////////////////// 317// Path precaching 318/////////////////////////////////////////////////////////////////////////////// 319 320PathCache::PathProcessor::PathProcessor(Caches& caches): 321 TaskProcessor<SkBitmap*>(&caches.tasks), mMaxTextureSize(caches.maxTextureSize) { 322} 323 324void PathCache::PathProcessor::onProcess(const sp<Task<SkBitmap*> >& task) { 325 PathTask* t = static_cast<PathTask*>(task.get()); 326 ATRACE_NAME("pathPrecache"); 327 328 float left, top, offset; 329 uint32_t width, height; 330 PathCache::computePathBounds(&t->path, &t->paint, left, top, offset, width, height); 331 332 PathTexture* texture = t->texture; 333 texture->left = left; 334 texture->top = top; 335 texture->offset = offset; 336 texture->width = width; 337 texture->height = height; 338 339 if (width <= mMaxTextureSize && height <= mMaxTextureSize) { 340 SkBitmap* bitmap = new SkBitmap(); 341 drawPath(&t->path, &t->paint, *bitmap, left, top, offset, width, height); 342 t->setResult(bitmap); 343 } else { 344 texture->width = 0; 345 texture->height = 0; 346 t->setResult(nullptr); 347 } 348} 349 350/////////////////////////////////////////////////////////////////////////////// 351// Paths 352/////////////////////////////////////////////////////////////////////////////// 353 354void PathCache::removeDeferred(const SkPath* path) { 355 Mutex::Autolock l(mLock); 356 mGarbage.push(path->getGenerationID()); 357} 358 359void PathCache::clearGarbage() { 360 Vector<PathDescription> pathsToRemove; 361 362 { // scope for the mutex 363 Mutex::Autolock l(mLock); 364 size_t count = mGarbage.size(); 365 for (size_t i = 0; i < count; i++) { 366 const uint32_t generationID = mGarbage.itemAt(i); 367 368 LruCache<PathDescription, PathTexture*>::Iterator iter(mCache); 369 while (iter.next()) { 370 const PathDescription& key = iter.key(); 371 if (key.type == kShapePath && key.shape.path.mGenerationID == generationID) { 372 pathsToRemove.push(key); 373 } 374 } 375 } 376 mGarbage.clear(); 377 } 378 379 for (size_t i = 0; i < pathsToRemove.size(); i++) { 380 mCache.remove(pathsToRemove.itemAt(i)); 381 } 382} 383 384PathTexture* PathCache::get(const SkPath* path, const SkPaint* paint) { 385 PathDescription entry(kShapePath, paint); 386 entry.shape.path.mGenerationID = path->getGenerationID(); 387 388 PathTexture* texture = mCache.get(entry); 389 390 if (!texture) { 391 texture = addTexture(entry, path, paint); 392 } else { 393 // A bitmap is attached to the texture, this means we need to 394 // upload it as a GL texture 395 const sp<Task<SkBitmap*> >& task = texture->task(); 396 if (task != nullptr) { 397 // But we must first wait for the worker thread to be done 398 // producing the bitmap, so let's wait 399 SkBitmap* bitmap = task->getResult(); 400 if (bitmap) { 401 generateTexture(entry, bitmap, texture, false); 402 texture->clearTask(); 403 } else { 404 ALOGW("Path too large to be rendered into a texture"); 405 texture->clearTask(); 406 texture = nullptr; 407 mCache.remove(entry); 408 } 409 } 410 } 411 412 return texture; 413} 414 415void PathCache::precache(const SkPath* path, const SkPaint* paint) { 416 if (!Caches::getInstance().tasks.canRunTasks()) { 417 return; 418 } 419 420 PathDescription entry(kShapePath, paint); 421 entry.shape.path.mGenerationID = path->getGenerationID(); 422 423 PathTexture* texture = mCache.get(entry); 424 425 bool generate = false; 426 if (!texture) { 427 generate = true; 428 } 429 430 if (generate) { 431 // It is important to specify the generation ID so we do not 432 // attempt to precache the same path several times 433 texture = new PathTexture(Caches::getInstance(), path->getGenerationID()); 434 sp<PathTask> task = new PathTask(path, paint, texture); 435 texture->setTask(task); 436 437 // During the precaching phase we insert path texture objects into 438 // the cache that do not point to any GL texture. They are instead 439 // treated as a task for the precaching worker thread. This is why 440 // we do not check the cache limit when inserting these objects. 441 // The conversion into GL texture will happen in get(), when a client 442 // asks for a path texture. This is also when the cache limit will 443 // be enforced. 444 mCache.put(entry, texture); 445 446 if (mProcessor == nullptr) { 447 mProcessor = new PathProcessor(Caches::getInstance()); 448 } 449 mProcessor->add(task); 450 } 451} 452 453/////////////////////////////////////////////////////////////////////////////// 454// Rounded rects 455/////////////////////////////////////////////////////////////////////////////// 456 457PathTexture* PathCache::getRoundRect(float width, float height, 458 float rx, float ry, const SkPaint* paint) { 459 PathDescription entry(kShapeRoundRect, paint); 460 entry.shape.roundRect.mWidth = width; 461 entry.shape.roundRect.mHeight = height; 462 entry.shape.roundRect.mRx = rx; 463 entry.shape.roundRect.mRy = ry; 464 465 PathTexture* texture = get(entry); 466 467 if (!texture) { 468 SkPath path; 469 SkRect r; 470 r.set(0.0f, 0.0f, width, height); 471 path.addRoundRect(r, rx, ry, SkPath::kCW_Direction); 472 473 texture = addTexture(entry, &path, paint); 474 } 475 476 return texture; 477} 478 479/////////////////////////////////////////////////////////////////////////////// 480// Circles 481/////////////////////////////////////////////////////////////////////////////// 482 483PathTexture* PathCache::getCircle(float radius, const SkPaint* paint) { 484 PathDescription entry(kShapeCircle, paint); 485 entry.shape.circle.mRadius = radius; 486 487 PathTexture* texture = get(entry); 488 489 if (!texture) { 490 SkPath path; 491 path.addCircle(radius, radius, radius, SkPath::kCW_Direction); 492 493 texture = addTexture(entry, &path, paint); 494 } 495 496 return texture; 497} 498 499/////////////////////////////////////////////////////////////////////////////// 500// Ovals 501/////////////////////////////////////////////////////////////////////////////// 502 503PathTexture* PathCache::getOval(float width, float height, const SkPaint* paint) { 504 PathDescription entry(kShapeOval, paint); 505 entry.shape.oval.mWidth = width; 506 entry.shape.oval.mHeight = height; 507 508 PathTexture* texture = get(entry); 509 510 if (!texture) { 511 SkPath path; 512 SkRect r; 513 r.set(0.0f, 0.0f, width, height); 514 path.addOval(r, SkPath::kCW_Direction); 515 516 texture = addTexture(entry, &path, paint); 517 } 518 519 return texture; 520} 521 522/////////////////////////////////////////////////////////////////////////////// 523// Rects 524/////////////////////////////////////////////////////////////////////////////// 525 526PathTexture* PathCache::getRect(float width, float height, const SkPaint* paint) { 527 PathDescription entry(kShapeRect, paint); 528 entry.shape.rect.mWidth = width; 529 entry.shape.rect.mHeight = height; 530 531 PathTexture* texture = get(entry); 532 533 if (!texture) { 534 SkPath path; 535 SkRect r; 536 r.set(0.0f, 0.0f, width, height); 537 path.addRect(r, SkPath::kCW_Direction); 538 539 texture = addTexture(entry, &path, paint); 540 } 541 542 return texture; 543} 544 545/////////////////////////////////////////////////////////////////////////////// 546// Arcs 547/////////////////////////////////////////////////////////////////////////////// 548 549PathTexture* PathCache::getArc(float width, float height, 550 float startAngle, float sweepAngle, bool useCenter, const SkPaint* paint) { 551 PathDescription entry(kShapeArc, paint); 552 entry.shape.arc.mWidth = width; 553 entry.shape.arc.mHeight = height; 554 entry.shape.arc.mStartAngle = startAngle; 555 entry.shape.arc.mSweepAngle = sweepAngle; 556 entry.shape.arc.mUseCenter = useCenter; 557 558 PathTexture* texture = get(entry); 559 560 if (!texture) { 561 SkPath path; 562 SkRect r; 563 r.set(0.0f, 0.0f, width, height); 564 if (useCenter) { 565 path.moveTo(r.centerX(), r.centerY()); 566 } 567 path.arcTo(r, startAngle, sweepAngle, !useCenter); 568 if (useCenter) { 569 path.close(); 570 } 571 572 texture = addTexture(entry, &path, paint); 573 } 574 575 return texture; 576} 577 578}; // namespace uirenderer 579}; // namespace android 580