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