Jens Gulin 6056e10271 Solve three memory leaks related to PatchCache
A Patch can be fairly large, holding bitmap data, but
is also frequently leaked which adds to the severity.
The feature is used in many important processes such
as Home, SystemUI and Chrome.

The following leaks are solved:

1. The Patch itself was not always freed.
PatchCache::removeDeferred() can mark patches to be
cared for by PatchCache::clearGarbage(). But
mCache.remove() would only destroy the container
and the pointer, not the Patch object itself.

2. The vertices stored in the Patch at Patch::createMesh()
would always leak. The empty/default destructor in Patch
would not properly destroy "vertices" since it's just a
pointer.

3. A BufferBlock that's added to the mFreeBlocks
in PatchCache could leak. The leak happened when a
patch later needed the entire free block, because the
object was removed from the list but never deleted
in PatchCache::setupMesh().

Change-Id: I41e60824479230b67426fc546d3dbff294c8891f
2014-03-25 09:37:02 +09:00

290 lines
8.1 KiB
C++

/*
* Copyright (C) 2010 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_TAG "OpenGLRenderer"
#include <utils/JenkinsHash.h>
#include <utils/Log.h>
#include "Caches.h"
#include "PatchCache.h"
#include "Properties.h"
namespace android {
namespace uirenderer {
///////////////////////////////////////////////////////////////////////////////
// Constructors/destructor
///////////////////////////////////////////////////////////////////////////////
PatchCache::PatchCache():
mSize(0), mCache(LruCache<PatchDescription, Patch*>::kUnlimitedCapacity),
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);
mMaxSize = KB(atoi(property));
} else {
INIT_LOGD(" Using default patch cache size of %.2fkB", DEFAULT_PATCH_CACHE_SIZE);
mMaxSize = KB(DEFAULT_PATCH_CACHE_SIZE);
}
}
PatchCache::~PatchCache() {
clear();
}
void PatchCache::init(Caches& caches) {
bool created = false;
if (!mMeshBuffer) {
glGenBuffers(1, &mMeshBuffer);
created = true;
}
caches.bindMeshBuffer(mMeshBuffer);
caches.resetVertexPointers();
if (created) {
createVertexBuffer();
}
}
///////////////////////////////////////////////////////////////////////////////
// Caching
///////////////////////////////////////////////////////////////////////////////
hash_t PatchCache::PatchDescription::hash() const {
uint32_t hash = JenkinsHashMix(0, android::hash_type(mPatch));
hash = JenkinsHashMix(hash, mBitmapWidth);
hash = JenkinsHashMix(hash, mBitmapHeight);
hash = JenkinsHashMix(hash, mPixelWidth);
hash = JenkinsHashMix(hash, mPixelHeight);
return JenkinsHashWhiten(hash);
}
int PatchCache::PatchDescription::compare(const PatchCache::PatchDescription& lhs,
const PatchCache::PatchDescription& rhs) {
return memcmp(&lhs, &rhs, sizeof(PatchDescription));
}
void PatchCache::clear() {
clearCache();
if (mMeshBuffer) {
Caches::getInstance().unbindMeshBuffer();
glDeleteBuffers(1, &mMeshBuffer);
mMeshBuffer = 0;
mSize = 0;
}
}
void PatchCache::clearCache() {
LruCache<PatchDescription, Patch*>::Iterator i(mCache);
while (i.next()) {
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);
// Assert that patch is not already garbage
size_t count = mGarbage.size();
for (size_t i = 0; i < count; i++) {
if (patch == mGarbage[i]) {
patch = NULL;
break;
}
}
LOG_ALWAYS_FATAL_IF(patch == NULL);
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++) {
Res_png_9patch* patch = mGarbage[i];
remove(patchesToRemove, patch);
// A Res_png_9patch is actually an array of byte that's larger
// than sizeof(Res_png_9patch). It must be freed as an array.
delete[] (int8_t*) patch;
}
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];
// Release the patch and mark the space in the free list
Patch* patch = pair.getSecond();
BufferBlock* block = new BufferBlock(patch->offset, patch->getSize());
block->next = mFreeBlocks;
mFreeBlocks = block;
mSize -= patch->getSize();
mCache.remove(*pair.getFirst());
delete patch;
}
#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;
}
delete block;
} 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) {
const PatchDescription description(bitmapWidth, bitmapHeight, pixelWidth, pixelHeight, patch);
const Patch* mesh = mCache.get(description);
if (!mesh) {
Patch* newMesh = new Patch();
TextureVertex* vertices;
if (entry) {
// An atlas entry has a UV mapper
vertices = newMesh->createMesh(bitmapWidth, bitmapHeight,
pixelWidth, pixelHeight, entry->uvMapper, patch);
} else {
vertices = newMesh->createMesh(bitmapWidth, bitmapHeight,
pixelWidth, pixelHeight, patch);
}
if (vertices) {
setupMesh(newMesh, vertices);
}
#if DEBUG_PATCHES
dumpFreeBlocks("Adding patch");
#endif
mCache.put(description, newMesh);
return newMesh;
}
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