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