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