Merge all shapes/paths caches to PathCache
This change will greatly simplify the multi-threading of all
shape types.
This change also uses PathTessellator to render convex paths.
Change-Id: I4e65bc95c9d24ecae2183b72204de5c2dfb6ada4
diff --git a/libs/hwui/PathCache.cpp b/libs/hwui/PathCache.cpp
index fb687cd..490c22a0 100644
--- a/libs/hwui/PathCache.cpp
+++ b/libs/hwui/PathCache.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,19 +15,301 @@
*/
#define LOG_TAG "OpenGLRenderer"
+#define ATRACE_TAG ATRACE_TAG_VIEW
-#include <utils/Mutex.h>
+#include <SkBitmap.h>
+#include <SkCanvas.h>
+#include <SkPaint.h>
+#include <SkPath.h>
+#include <SkRect.h>
-#include <sys/sysinfo.h>
+#include <utils/JenkinsHash.h>
+#include <utils/Trace.h>
#include "Caches.h"
#include "PathCache.h"
-#include "Properties.h"
+
+#include "thread/Signal.h"
+#include "thread/Task.h"
+#include "thread/TaskProcessor.h"
namespace android {
namespace uirenderer {
///////////////////////////////////////////////////////////////////////////////
+// Cache entries
+///////////////////////////////////////////////////////////////////////////////
+
+PathDescription::PathDescription():
+ type(kShapeNone),
+ join(SkPaint::kDefault_Join),
+ cap(SkPaint::kDefault_Cap),
+ style(SkPaint::kFill_Style),
+ miter(4.0f),
+ strokeWidth(1.0f),
+ pathEffect(NULL) {
+ memset(&shape, 0, sizeof(Shape));
+}
+
+PathDescription::PathDescription(ShapeType type, SkPaint* paint):
+ type(type),
+ join(paint->getStrokeJoin()),
+ cap(paint->getStrokeCap()),
+ style(paint->getStyle()),
+ miter(paint->getStrokeMiter()),
+ strokeWidth(paint->getStrokeWidth()),
+ pathEffect(paint->getPathEffect()) {
+ memset(&shape, 0, sizeof(Shape));
+}
+
+hash_t PathDescription::hash() const {
+ uint32_t hash = JenkinsHashMix(0, type);
+ hash = JenkinsHashMix(hash, join);
+ hash = JenkinsHashMix(hash, cap);
+ hash = JenkinsHashMix(hash, style);
+ hash = JenkinsHashMix(hash, android::hash_type(miter));
+ hash = JenkinsHashMix(hash, android::hash_type(strokeWidth));
+ hash = JenkinsHashMix(hash, android::hash_type(pathEffect));
+ hash = JenkinsHashMixBytes(hash, (uint8_t*) &shape, sizeof(Shape));
+ return JenkinsHashWhiten(hash);
+}
+
+int PathDescription::compare(const PathDescription& rhs) const {
+ return memcmp(this, &rhs, sizeof(PathDescription));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Utilities
+///////////////////////////////////////////////////////////////////////////////
+
+bool PathCache::canDrawAsConvexPath(SkPath* path, SkPaint* paint) {
+ // NOTE: This should only be used after PathTessellator handles joins properly
+ return paint->getPathEffect() == NULL && path->getConvexity() == SkPath::kConvex_Convexity;
+}
+
+void PathCache::computePathBounds(const SkPath* path, const SkPaint* paint,
+ float& left, float& top, float& offset, uint32_t& width, uint32_t& height) {
+ const SkRect& bounds = path->getBounds();
+ PathCache::computeBounds(bounds, paint, left, top, offset, width, height);
+}
+
+void PathCache::computeBounds(const SkRect& bounds, const SkPaint* paint,
+ float& left, float& top, float& offset, uint32_t& width, uint32_t& height) {
+ const float pathWidth = fmax(bounds.width(), 1.0f);
+ const float pathHeight = fmax(bounds.height(), 1.0f);
+
+ left = bounds.fLeft;
+ top = bounds.fTop;
+
+ offset = (int) floorf(fmax(paint->getStrokeWidth(), 1.0f) * 1.5f + 0.5f);
+
+ width = uint32_t(pathWidth + offset * 2.0 + 0.5);
+ height = uint32_t(pathHeight + offset * 2.0 + 0.5);
+}
+
+static void initBitmap(SkBitmap& bitmap, uint32_t width, uint32_t height) {
+ bitmap.setConfig(SkBitmap::kA8_Config, width, height);
+ bitmap.allocPixels();
+ bitmap.eraseColor(0);
+}
+
+static void initPaint(SkPaint& paint) {
+ // Make sure the paint is opaque, color, alpha, filter, etc.
+ // will be applied later when compositing the alpha8 texture
+ paint.setColor(0xff000000);
+ paint.setAlpha(255);
+ paint.setColorFilter(NULL);
+ paint.setMaskFilter(NULL);
+ paint.setShader(NULL);
+ SkXfermode* mode = SkXfermode::Create(SkXfermode::kSrc_Mode);
+ SkSafeUnref(paint.setXfermode(mode));
+}
+
+static void drawPath(const SkPath *path, const SkPaint* paint, SkBitmap& bitmap,
+ float left, float top, float offset, uint32_t width, uint32_t height) {
+ initBitmap(bitmap, width, height);
+
+ SkPaint pathPaint(*paint);
+ initPaint(pathPaint);
+
+ SkCanvas canvas(bitmap);
+ canvas.translate(-left + offset, -top + offset);
+ canvas.drawPath(*path, pathPaint);
+}
+
+static PathTexture* createTexture(float left, float top, float offset,
+ uint32_t width, uint32_t height, uint32_t id) {
+ PathTexture* texture = new PathTexture();
+ texture->left = left;
+ texture->top = top;
+ texture->offset = offset;
+ texture->width = width;
+ texture->height = height;
+ texture->generation = id;
+ return texture;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Cache constructor/destructor
+///////////////////////////////////////////////////////////////////////////////
+
+PathCache::PathCache():
+ mCache(LruCache<PathDescription, PathTexture*>::kUnlimitedCapacity),
+ mSize(0), mMaxSize(MB(DEFAULT_PATH_CACHE_SIZE)) {
+ char property[PROPERTY_VALUE_MAX];
+ if (property_get(PROPERTY_PATH_CACHE_SIZE, property, NULL) > 0) {
+ INIT_LOGD(" Setting %s cache size to %sMB", name, property);
+ setMaxSize(MB(atof(property)));
+ } else {
+ INIT_LOGD(" Using default %s cache size of %.2fMB", name, DEFAULT_PATH_CACHE_SIZE);
+ }
+ init();
+}
+
+PathCache::~PathCache() {
+ mCache.clear();
+}
+
+void PathCache::init() {
+ mCache.setOnEntryRemovedListener(this);
+
+ GLint maxTextureSize;
+ glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);
+ mMaxTextureSize = maxTextureSize;
+
+ mDebugEnabled = readDebugLevel() & kDebugCaches;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Size management
+///////////////////////////////////////////////////////////////////////////////
+
+uint32_t PathCache::getSize() {
+ return mSize;
+}
+
+uint32_t PathCache::getMaxSize() {
+ return mMaxSize;
+}
+
+void PathCache::setMaxSize(uint32_t maxSize) {
+ mMaxSize = maxSize;
+ while (mSize > mMaxSize) {
+ mCache.removeOldest();
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Callbacks
+///////////////////////////////////////////////////////////////////////////////
+
+void PathCache::operator()(PathDescription& entry, PathTexture*& texture) {
+ removeTexture(texture);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Caching
+///////////////////////////////////////////////////////////////////////////////
+
+void PathCache::removeTexture(PathTexture* texture) {
+ if (texture) {
+ const uint32_t size = texture->width * texture->height;
+ mSize -= size;
+
+ PATH_LOGD("PathCache::delete name, size, mSize = %d, %d, %d",
+ texture->id, size, mSize);
+ if (mDebugEnabled) {
+ ALOGD("Shape deleted, size = %d", size);
+ }
+
+ if (texture->id) {
+ glDeleteTextures(1, &texture->id);
+ }
+ delete texture;
+ }
+}
+
+void PathCache::purgeCache(uint32_t width, uint32_t height) {
+ const uint32_t size = width * height;
+ // Don't even try to cache a bitmap that's bigger than the cache
+ if (size < mMaxSize) {
+ while (mSize + size > mMaxSize) {
+ mCache.removeOldest();
+ }
+ }
+}
+
+void PathCache::trim() {
+ while (mSize > mMaxSize) {
+ mCache.removeOldest();
+ }
+}
+
+PathTexture* PathCache::addTexture(const PathDescription& entry, const SkPath *path,
+ const SkPaint* paint) {
+ ATRACE_CALL();
+
+ float left, top, offset;
+ uint32_t width, height;
+ computePathBounds(path, paint, left, top, offset, width, height);
+
+ if (!checkTextureSize(width, height)) return NULL;
+
+ purgeCache(width, height);
+
+ SkBitmap bitmap;
+ drawPath(path, paint, bitmap, left, top, offset, width, height);
+
+ PathTexture* texture = createTexture(left, top, offset, width, height,
+ path->getGenerationID());
+ addTexture(entry, &bitmap, texture);
+
+ return texture;
+}
+
+void PathCache::addTexture(const PathDescription& entry, SkBitmap* bitmap, PathTexture* texture) {
+ generateTexture(*bitmap, texture);
+
+ uint32_t size = texture->width * texture->height;
+ if (size < mMaxSize) {
+ mSize += size;
+ PATH_LOGD("PathCache::get/create: name, size, mSize = %d, %d, %d",
+ texture->id, size, mSize);
+ if (mDebugEnabled) {
+ ALOGD("Shape created, size = %d", size);
+ }
+ mCache.put(entry, texture);
+ } else {
+ texture->cleanup = true;
+ }
+}
+
+void PathCache::clear() {
+ mCache.clear();
+}
+
+void PathCache::generateTexture(SkBitmap& bitmap, Texture* texture) {
+ SkAutoLockPixels alp(bitmap);
+ if (!bitmap.readyToDraw()) {
+ ALOGE("Cannot generate texture from bitmap");
+ return;
+ }
+
+ glGenTextures(1, &texture->id);
+
+ glBindTexture(GL_TEXTURE_2D, texture->id);
+ // Textures are Alpha8
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+ texture->blend = true;
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, texture->width, texture->height, 0,
+ GL_ALPHA, GL_UNSIGNED_BYTE, bitmap.getPixels());
+
+ texture->setFilter(GL_LINEAR);
+ texture->setWrap(GL_CLAMP_TO_EDGE);
+}
+
+///////////////////////////////////////////////////////////////////////////////
// Path precaching
///////////////////////////////////////////////////////////////////////////////
@@ -52,7 +334,7 @@
if (width <= mMaxTextureSize && height <= mMaxTextureSize) {
SkBitmap* bitmap = new SkBitmap();
- PathCache::drawPath(t->path, t->paint, *bitmap, left, top, offset, width, height);
+ drawPath(t->path, t->paint, *bitmap, left, top, offset, width, height);
t->setResult(bitmap);
} else {
texture->width = 0;
@@ -62,23 +344,17 @@
}
///////////////////////////////////////////////////////////////////////////////
-// Path cache
+// Paths
///////////////////////////////////////////////////////////////////////////////
-PathCache::PathCache(): ShapeCache<PathCacheEntry>("path",
- PROPERTY_PATH_CACHE_SIZE, DEFAULT_PATH_CACHE_SIZE) {
-}
-
-PathCache::~PathCache() {
-}
-
void PathCache::remove(SkPath* path) {
- Vector<PathCacheEntry> pathsToRemove;
- LruCache<PathCacheEntry, PathTexture*>::Iterator i(mCache);
+ Vector<PathDescription> pathsToRemove;
+ LruCache<PathDescription, PathTexture*>::Iterator i(mCache);
while (i.next()) {
- const PathCacheEntry& key = i.key();
- if (key.path == path || key.path == path->getSourcePath()) {
+ const PathDescription& key = i.key();
+ if (key.type == kShapePath &&
+ (key.shape.path.mPath == path || key.shape.path.mPath == path->getSourcePath())) {
pathsToRemove.push(key);
}
}
@@ -121,7 +397,9 @@
PathTexture* PathCache::get(SkPath* path, SkPaint* paint) {
path = getSourcePath(path);
- PathCacheEntry entry(path, paint);
+ PathDescription entry(kShapePath, paint);
+ entry.shape.path.mPath = path;
+
PathTexture* texture = mCache.get(entry);
if (!texture) {
@@ -159,7 +437,9 @@
path = getSourcePath(path);
- PathCacheEntry entry(path, paint);
+ PathDescription entry(kShapePath, paint);
+ entry.shape.path.mPath = path;
+
PathTexture* texture = mCache.get(entry);
bool generate = false;
@@ -193,5 +473,130 @@
}
}
+///////////////////////////////////////////////////////////////////////////////
+// Rounded rects
+///////////////////////////////////////////////////////////////////////////////
+
+PathTexture* PathCache::getRoundRect(float width, float height,
+ float rx, float ry, SkPaint* paint) {
+ PathDescription entry(kShapeRoundRect, paint);
+ entry.shape.roundRect.mWidth = width;
+ entry.shape.roundRect.mHeight = height;
+ entry.shape.roundRect.mRx = rx;
+ entry.shape.roundRect.mRy = ry;
+
+ PathTexture* texture = get(entry);
+
+ if (!texture) {
+ SkPath path;
+ SkRect r;
+ r.set(0.0f, 0.0f, width, height);
+ path.addRoundRect(r, rx, ry, SkPath::kCW_Direction);
+
+ texture = addTexture(entry, &path, paint);
+ }
+
+ return texture;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Circles
+///////////////////////////////////////////////////////////////////////////////
+
+PathTexture* PathCache::getCircle(float radius, SkPaint* paint) {
+ PathDescription entry(kShapeCircle, paint);
+ entry.shape.circle.mRadius = radius;
+
+ PathTexture* texture = get(entry);
+
+ if (!texture) {
+ SkPath path;
+ path.addCircle(radius, radius, radius, SkPath::kCW_Direction);
+
+ texture = addTexture(entry, &path, paint);
+ }
+
+ return texture;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Ovals
+///////////////////////////////////////////////////////////////////////////////
+
+PathTexture* PathCache::getOval(float width, float height, SkPaint* paint) {
+ PathDescription entry(kShapeOval, paint);
+ entry.shape.oval.mWidth = width;
+ entry.shape.oval.mHeight = height;
+
+ PathTexture* texture = get(entry);
+
+ if (!texture) {
+ SkPath path;
+ SkRect r;
+ r.set(0.0f, 0.0f, width, height);
+ path.addOval(r, SkPath::kCW_Direction);
+
+ texture = addTexture(entry, &path, paint);
+ }
+
+ return texture;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Rects
+///////////////////////////////////////////////////////////////////////////////
+
+PathTexture* PathCache::getRect(float width, float height, SkPaint* paint) {
+ PathDescription entry(kShapeRect, paint);
+ entry.shape.rect.mWidth = width;
+ entry.shape.rect.mHeight = height;
+
+ PathTexture* texture = get(entry);
+
+ if (!texture) {
+ SkPath path;
+ SkRect r;
+ r.set(0.0f, 0.0f, width, height);
+ path.addRect(r, SkPath::kCW_Direction);
+
+ texture = addTexture(entry, &path, paint);
+ }
+
+ return texture;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Arcs
+///////////////////////////////////////////////////////////////////////////////
+
+PathTexture* PathCache::getArc(float width, float height,
+ float startAngle, float sweepAngle, bool useCenter, SkPaint* paint) {
+ PathDescription entry(kShapeArc, paint);
+ entry.shape.arc.mWidth = width;
+ entry.shape.arc.mHeight = height;
+ entry.shape.arc.mStartAngle = startAngle;
+ entry.shape.arc.mSweepAngle = sweepAngle;
+ entry.shape.arc.mUseCenter = useCenter;
+
+ PathTexture* texture = get(entry);
+
+ if (!texture) {
+ SkPath path;
+ SkRect r;
+ r.set(0.0f, 0.0f, width, height);
+ if (useCenter) {
+ path.moveTo(r.centerX(), r.centerY());
+ }
+ path.arcTo(r, startAngle, sweepAngle, !useCenter);
+ if (useCenter) {
+ path.close();
+ }
+
+ texture = addTexture(entry, &path, paint);
+ }
+
+ return texture;
+}
+
}; // namespace uirenderer
}; // namespace android