Precache paths from a worker thread

Change-Id: I3e7b53d67e0e03e403beaf55c39350ead7f1e309
diff --git a/libs/hwui/PathCache.cpp b/libs/hwui/PathCache.cpp
index 4f682ed..9e6ec84 100644
--- a/libs/hwui/PathCache.cpp
+++ b/libs/hwui/PathCache.cpp
@@ -16,34 +16,84 @@
 
 #define LOG_TAG "OpenGLRenderer"
 
-#include <utils/threads.h>
+#include <utils/Mutex.h>
 
+#include <sys/sysinfo.h>
+
+#include "Caches.h"
 #include "PathCache.h"
 #include "Properties.h"
 
 namespace android {
 namespace uirenderer {
 
-// Defined in ShapeCache.h
+///////////////////////////////////////////////////////////////////////////////
+// Path precaching
+///////////////////////////////////////////////////////////////////////////////
 
-void computePathBounds(const SkPath* path, const SkPaint* paint,
-        float& left, float& top, float& offset, uint32_t& width, uint32_t& height) {
-    const SkRect& bounds = path->getBounds();
-    computeBounds(bounds, paint, left, top, offset, width, height);
+bool PathCache::PrecacheThread::threadLoop() {
+    mSignal.wait();
+    Vector<Task> tasks;
+    {
+        Mutex::Autolock l(mLock);
+        tasks = mTasks;
+        mTasks.clear();
+    }
+
+    Caches& caches = Caches::getInstance();
+    uint32_t maxSize = caches.maxTextureSize;
+
+    ATRACE_BEGIN("pathPrecache");
+    for (size_t i = 0; i < tasks.size(); i++) {
+        const Task& task = tasks.itemAt(i);
+
+        float left, top, offset;
+        uint32_t width, height;
+        PathCache::computePathBounds(task.path, task.paint, left, top, offset, width, height);
+
+        if (width <= maxSize && height <= maxSize) {
+            SkBitmap* bitmap = new SkBitmap();
+
+            PathTexture* texture = task.texture;
+            texture->left = left;
+            texture->top = top;
+            texture->offset = offset;
+            texture->width = width;
+            texture->height = height;
+
+            PathCache::drawPath(task.path, task.paint, *bitmap, left, top, offset, width, height);
+
+            texture->future()->produce(bitmap);
+        } else {
+            task.texture->future()->produce(NULL);
+        }
+    }
+    ATRACE_END();
+    return true;
 }
 
-void 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);
+void PathCache::PrecacheThread::addTask(PathTexture* texture, SkPath* path, SkPaint* paint) {
+    if (!isRunning()) {
+        run("libhwui:pathPrecache", PRIORITY_DEFAULT);
+    }
 
-    left = bounds.fLeft;
-    top = bounds.fTop;
+    Task task;
+    task.texture = texture;
+    task.path = path;
+    task.paint = paint;
 
-    offset = (int) floorf(fmax(paint->getStrokeWidth(), 1.0f) * 1.5f + 0.5f);
+    Mutex::Autolock l(mLock);
+    mTasks.add(task);
+    mSignal.signal();
+}
 
-    width = uint32_t(pathWidth + offset * 2.0 + 0.5);
-    height = uint32_t(pathHeight + offset * 2.0 + 0.5);
+void PathCache::PrecacheThread::exit() {
+    {
+        Mutex::Autolock l(mLock);
+        mTasks.clear();
+    }
+    requestExit();
+    mSignal.signal();
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -51,7 +101,11 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 PathCache::PathCache(): ShapeCache<PathCacheEntry>("path",
-        PROPERTY_PATH_CACHE_SIZE, DEFAULT_PATH_CACHE_SIZE) {
+        PROPERTY_PATH_CACHE_SIZE, DEFAULT_PATH_CACHE_SIZE), mThread(new PrecacheThread()) {
+}
+
+PathCache::~PathCache() {
+    mThread->exit();
 }
 
 void PathCache::remove(SkPath* path) {
@@ -60,7 +114,7 @@
 
     while (i.next()) {
         const PathCacheEntry& key = i.key();
-        if (key.path == path) {
+        if (key.path == path || key.path == path->getSourcePath()) {
             pathsToRemove.push(key);
         }
     }
@@ -71,12 +125,12 @@
 }
 
 void PathCache::removeDeferred(SkPath* path) {
-    Mutex::Autolock _l(mLock);
+    Mutex::Autolock l(mLock);
     mGarbage.push(path);
 }
 
 void PathCache::clearGarbage() {
-    Mutex::Autolock _l(mLock);
+    Mutex::Autolock l(mLock);
     size_t count = mGarbage.size();
     for (size_t i = 0; i < count; i++) {
         remove(mGarbage.itemAt(i));
@@ -84,24 +138,86 @@
     mGarbage.clear();
 }
 
-PathTexture* PathCache::get(SkPath* path, SkPaint* paint) {
+/**
+ * To properly handle path mutations at draw time we always make a copy
+ * of paths objects when recording display lists. The source path points
+ * to the path we originally copied the path from. This ensures we use
+ * the original path as a cache key the first time a path is inserted
+ * in the cache. The source path is also used to reclaim garbage when a
+ * Dalvik Path object is collected.
+ */
+static SkPath* getSourcePath(SkPath* path) {
     const SkPath* sourcePath = path->getSourcePath();
     if (sourcePath && sourcePath->getGenerationID() == path->getGenerationID()) {
-        path = const_cast<SkPath*>(sourcePath);
+        return const_cast<SkPath*>(sourcePath);
     }
+    return path;
+}
+
+PathTexture* PathCache::get(SkPath* path, SkPaint* paint) {
+    path = getSourcePath(path);
 
     PathCacheEntry entry(path, paint);
     PathTexture* texture = mCache.get(entry);
 
     if (!texture) {
         texture = addTexture(entry, path, paint);
-    } else if (path->getGenerationID() != texture->generation) {
-        mCache.remove(entry);
-        texture = addTexture(entry, path, paint);
+    } else {
+        // A bitmap is attached to the texture, this means we need to
+        // upload it as a GL texture
+        if (texture->future() != NULL) {
+            // But we must first wait for the worker thread to be done
+            // producing the bitmap, so let's wait
+            SkBitmap* bitmap = texture->future()->get();
+            if (bitmap) {
+                addTexture(entry, bitmap, texture);
+                texture->clearFuture();
+            } else {
+                ALOGW("Path too large to be rendered into a texture (%dx%d)",
+                        texture->width, texture->height);
+                texture->clearFuture();
+                texture = NULL;
+                mCache.remove(entry);
+            }
+        } else if (path->getGenerationID() != texture->generation) {
+            mCache.remove(entry);
+            texture = addTexture(entry, path, paint);
+        }
     }
 
     return texture;
 }
 
+void PathCache::precache(SkPath* path, SkPaint* paint) {
+    path = getSourcePath(path);
+
+    PathCacheEntry entry(path, paint);
+    PathTexture* texture = mCache.get(entry);
+
+    bool generate = false;
+    if (!texture) {
+        generate = true;
+    } else if (path->getGenerationID() != texture->generation) {
+        mCache.remove(entry);
+        generate = true;
+    }
+
+    if (generate) {
+        // It is important to specify the generation ID so we do not
+        // attempt to precache the same path several times
+        texture = createTexture(0.0f, 0.0f, 0.0f, 0, 0, path->getGenerationID(), true);
+
+        // During the precaching phase we insert path texture objects into
+        // the cache that do not point to any GL texture. They are instead
+        // treated as a task for the precaching worker thread. This is why
+        // we do not check the cache limit when inserting these objects.
+        // The conversion into GL texture will happen in get(), when a client
+        // asks for a path texture. This is also when the cache limit will
+        // be enforced.
+        mCache.put(entry, texture);
+        mThread->addTask(texture, path, paint);
+    }
+}
+
 }; // namespace uirenderer
 }; // namespace android