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..490c22a 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