Refcount 9-patches and properly handle GC events

This change adds refcounting of Res_png_9patch instances, the native
data structure used to represent 9-patches. The Dalvik NinePatch class
now holds a native pointer instead of a Dalvik byte[]. This pointer
is used whenever we need to draw the 9-patch (software or hardware.)

Since we are now tracking garbage collection of NinePatch objects
libhwui's PatchCache must keep a list of free blocks in the VBO
used to store the meshes.

This change also removes unnecessary instances tracking from
GLES20DisplayList. Bitmaps and 9-patches are refcounted at the
native level and do not need to be tracked by the Dalvik layer.

Change-Id: Ib8682d573a538aaf1945f8ec5a9bd5da5d16f74b
diff --git a/libs/hwui/PatchCache.cpp b/libs/hwui/PatchCache.cpp
index dc69d7f..dc0d98c 100644
--- a/libs/hwui/PatchCache.cpp
+++ b/libs/hwui/PatchCache.cpp
@@ -32,7 +32,7 @@
 
 PatchCache::PatchCache():
         mSize(0), mCache(LruCache<PatchDescription, Patch*>::kUnlimitedCapacity),
-        mMeshBuffer(0), mGenerationId(0) {
+        mMeshBuffer(0), mFreeBlocks(NULL), mGenerationId(0) {
     char property[PROPERTY_VALUE_MAX];
     if (property_get(PROPERTY_PATCH_CACHE_SIZE, property, NULL) > 0) {
         INIT_LOGD("  Setting patch cache size to %skB", property);
@@ -97,14 +97,130 @@
         delete i.value();
     }
     mCache.clear();
+
+    BufferBlock* block = mFreeBlocks;
+    while (block) {
+        BufferBlock* next = block->next;
+        delete block;
+        block = next;
+    }
+    mFreeBlocks = NULL;
+}
+
+void PatchCache::remove(Vector<patch_pair_t>& patchesToRemove, Res_png_9patch* patch) {
+    LruCache<PatchDescription, Patch*>::Iterator i(mCache);
+    while (i.next()) {
+        const PatchDescription& key = i.key();
+        if (key.getPatch() == patch) {
+            patchesToRemove.push(patch_pair_t(&key, i.value()));
+        }
+    }
+}
+
+void PatchCache::removeDeferred(Res_png_9patch* patch) {
+    Mutex::Autolock _l(mLock);
+    mGarbage.push(patch);
+}
+
+void PatchCache::clearGarbage() {
+    Vector<patch_pair_t> patchesToRemove;
+
+    { // scope for the mutex
+        Mutex::Autolock _l(mLock);
+        size_t count = mGarbage.size();
+        for (size_t i = 0; i < count; i++) {
+            remove(patchesToRemove, mGarbage[i]);
+        }
+        mGarbage.clear();
+    }
+
+    // TODO: We could sort patchesToRemove by offset to merge
+    // adjacent free blocks
+    for (size_t i = 0; i < patchesToRemove.size(); i++) {
+        const patch_pair_t& pair = patchesToRemove[i];
+
+        // Add a new free block to the list
+        const Patch* patch = pair.getSecond();
+        BufferBlock* block = new BufferBlock(patch->offset, patch->getSize());
+        block->next = mFreeBlocks;
+        mFreeBlocks = block;
+
+        mSize -= patch->getSize();
+
+        mCache.remove(*pair.getFirst());
+    }
+
+#if DEBUG_PATCHES
+    if (patchesToRemove.size() > 0) {
+        dumpFreeBlocks("Removed garbage");
+    }
+#endif
 }
 
 void PatchCache::createVertexBuffer() {
     glBufferData(GL_ARRAY_BUFFER, mMaxSize, NULL, GL_DYNAMIC_DRAW);
     mSize = 0;
+    mFreeBlocks = new BufferBlock(0, mMaxSize);
     mGenerationId++;
 }
 
+/**
+ * Sets the mesh's offsets and copies its associated vertices into
+ * the mesh buffer (VBO).
+ */
+void PatchCache::setupMesh(Patch* newMesh, TextureVertex* vertices) {
+    // This call ensures the VBO exists and that it is bound
+    init(Caches::getInstance());
+
+    // If we're running out of space, let's clear the entire cache
+    uint32_t size = newMesh->getSize();
+    if (mSize + size > mMaxSize) {
+        clearCache();
+        createVertexBuffer();
+    }
+
+    // Find a block where we can fit the mesh
+    BufferBlock* previous = NULL;
+    BufferBlock* block = mFreeBlocks;
+    while (block) {
+        // The mesh fits
+        if (block->size >= size) {
+            break;
+        }
+        previous = block;
+        block = block->next;
+    }
+
+    // We have enough space left in the buffer, but it's
+    // too fragmented, let's clear the cache
+    if (!block) {
+        clearCache();
+        createVertexBuffer();
+        previous = NULL;
+        block = mFreeBlocks;
+    }
+
+    // Copy the 9patch mesh in the VBO
+    newMesh->offset = (GLintptr) (block->offset);
+    newMesh->textureOffset = newMesh->offset + gMeshTextureOffset;
+    glBufferSubData(GL_ARRAY_BUFFER, newMesh->offset, size, vertices);
+
+    // Remove the block since we've used it entirely
+    if (block->size == size) {
+        if (previous) {
+            previous->next = block->next;
+        } else {
+            mFreeBlocks = block->next;
+        }
+    } else {
+        // Resize the block now that it's occupied
+        block->offset += size;
+        block->size -= size;
+    }
+
+    mSize += size;
+}
+
 const Patch* PatchCache::get(const AssetAtlas::Entry* entry,
         const uint32_t bitmapWidth, const uint32_t bitmapHeight,
         const float pixelWidth, const float pixelHeight, const Res_png_9patch* patch) {
@@ -117,6 +233,7 @@
         TextureVertex* vertices;
 
         if (entry) {
+            // An atlas entry has a UV mapper
             vertices = newMesh->createMesh(bitmapWidth, bitmapHeight,
                     pixelWidth, pixelHeight, entry->uvMapper, patch);
         } else {
@@ -125,24 +242,13 @@
         }
 
         if (vertices) {
-            // This call ensures the VBO exists and that it is bound
-            init(Caches::getInstance());
-
-            // TODO: Simply remove the oldest items until we have enough room
-            // This will require to keep a list of free blocks in the VBO
-            uint32_t size = newMesh->getSize();
-            if (mSize + size > mMaxSize) {
-                clearCache();
-                createVertexBuffer();
-            }
-
-            newMesh->offset = (GLintptr) mSize;
-            newMesh->textureOffset = newMesh->offset + gMeshTextureOffset;
-            mSize += size;
-
-            glBufferSubData(GL_ARRAY_BUFFER, newMesh->offset, size, vertices);
+            setupMesh(newMesh, vertices);
         }
 
+#if DEBUG_PATCHES
+        dumpFreeBlocks("Adding patch");
+#endif
+
         mCache.put(description, newMesh);
         return newMesh;
     }
@@ -150,5 +256,17 @@
     return mesh;
 }
 
+#if DEBUG_PATCHES
+void PatchCache::dumpFreeBlocks(const char* prefix) {
+    String8 dump;
+    BufferBlock* block = mFreeBlocks;
+    while (block) {
+        dump.appendFormat("->(%d, %d)", block->offset, block->size);
+        block = block->next;
+    }
+    ALOGD("%s: Free blocks%s", prefix, dump.string());
+}
+#endif
+
 }; // namespace uirenderer
 }; // namespace android