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