Some caches(PatchCache, TextureCache, PathCache) for HWUI uses deferred removal for their cache entries even though actual resource objects are immediately freed by ResourceCache. For this reason, the uniqueness of a resource address in the caches is not guaranteed in specific cases. (Because malloc() can return the same address when malloc() and free() called very frequently.) So it can be possible the cache have two cache entries for two different resources but the same memory address. (Of course one of the resources is already freed.) It also can be possible mGarbage vector in PatchCache has duplicated addresses and this can lead to duplicated free blocks in the free block list and graphics corruption. (Deferred removal was implmeneted based on an assumption of unique resource addresses.) So this patch makes sure resource objects are freed after the resources are removed from the caches to guarantee the uniqueness of a resource address and prevent graphics corruption. Change-Id: I040f033a4fc783d2c4bc04b113589657c36fb15b Signed-off-by: Sangkyu Lee <sk82.lee@lge.com>
277 lines
7.8 KiB
C++
277 lines
7.8 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);
|
|
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];
|
|
|
|
// 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) {
|
|
|
|
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
|