Files
android_frameworks_base/libs/hwui/TextureCache.cpp
Sangkyu Lee 36fad8f6fc Fix graphics corruption caused by HWUI caches
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>
2014-02-26 10:43:26 -08:00

324 lines
10 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 <GLES2/gl2.h>
#include <SkCanvas.h>
#include <utils/Mutex.h>
#include "Caches.h"
#include "TextureCache.h"
#include "Properties.h"
namespace android {
namespace uirenderer {
///////////////////////////////////////////////////////////////////////////////
// Constructors/destructor
///////////////////////////////////////////////////////////////////////////////
TextureCache::TextureCache():
mCache(LruCache<SkBitmap*, Texture*>::kUnlimitedCapacity),
mSize(0), mMaxSize(MB(DEFAULT_TEXTURE_CACHE_SIZE)),
mFlushRate(DEFAULT_TEXTURE_CACHE_FLUSH_RATE) {
char property[PROPERTY_VALUE_MAX];
if (property_get(PROPERTY_TEXTURE_CACHE_SIZE, property, NULL) > 0) {
INIT_LOGD(" Setting texture cache size to %sMB", property);
setMaxSize(MB(atof(property)));
} else {
INIT_LOGD(" Using default texture cache size of %.2fMB", DEFAULT_TEXTURE_CACHE_SIZE);
}
if (property_get(PROPERTY_TEXTURE_CACHE_FLUSH_RATE, property, NULL) > 0) {
float flushRate = atof(property);
INIT_LOGD(" Setting texture cache flush rate to %.2f%%", flushRate * 100.0f);
setFlushRate(flushRate);
} else {
INIT_LOGD(" Using default texture cache flush rate of %.2f%%",
DEFAULT_TEXTURE_CACHE_FLUSH_RATE * 100.0f);
}
init();
}
TextureCache::TextureCache(uint32_t maxByteSize):
mCache(LruCache<SkBitmap*, Texture*>::kUnlimitedCapacity),
mSize(0), mMaxSize(maxByteSize) {
init();
}
TextureCache::~TextureCache() {
mCache.clear();
}
void TextureCache::init() {
mCache.setOnEntryRemovedListener(this);
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &mMaxTextureSize);
INIT_LOGD(" Maximum texture dimension is %d pixels", mMaxTextureSize);
mDebugEnabled = readDebugLevel() & kDebugCaches;
}
///////////////////////////////////////////////////////////////////////////////
// Size management
///////////////////////////////////////////////////////////////////////////////
uint32_t TextureCache::getSize() {
return mSize;
}
uint32_t TextureCache::getMaxSize() {
return mMaxSize;
}
void TextureCache::setMaxSize(uint32_t maxSize) {
mMaxSize = maxSize;
while (mSize > mMaxSize) {
mCache.removeOldest();
}
}
void TextureCache::setFlushRate(float flushRate) {
mFlushRate = fmaxf(0.0f, fminf(1.0f, flushRate));
}
///////////////////////////////////////////////////////////////////////////////
// Callbacks
///////////////////////////////////////////////////////////////////////////////
void TextureCache::operator()(SkBitmap*& bitmap, Texture*& texture) {
// This will be called already locked
if (texture) {
mSize -= texture->bitmapSize;
TEXTURE_LOGD("TextureCache::callback: name, removed size, mSize = %d, %d, %d",
texture->id, texture->bitmapSize, mSize);
if (mDebugEnabled) {
ALOGD("Texture deleted, size = %d", texture->bitmapSize);
}
texture->deleteTexture();
delete texture;
}
}
///////////////////////////////////////////////////////////////////////////////
// Caching
///////////////////////////////////////////////////////////////////////////////
Texture* TextureCache::get(SkBitmap* bitmap) {
Texture* texture = mCache.get(bitmap);
if (!texture) {
if (bitmap->width() > mMaxTextureSize || bitmap->height() > mMaxTextureSize) {
ALOGW("Bitmap too large to be uploaded into a texture (%dx%d, max=%dx%d)",
bitmap->width(), bitmap->height(), mMaxTextureSize, mMaxTextureSize);
return NULL;
}
const uint32_t size = bitmap->rowBytes() * bitmap->height();
// Don't even try to cache a bitmap that's bigger than the cache
if (size < mMaxSize) {
while (mSize + size > mMaxSize) {
mCache.removeOldest();
}
}
texture = new Texture();
texture->bitmapSize = size;
generateTexture(bitmap, texture, false);
if (size < mMaxSize) {
mSize += size;
TEXTURE_LOGD("TextureCache::get: create texture(%p): name, size, mSize = %d, %d, %d",
bitmap, texture->id, size, mSize);
if (mDebugEnabled) {
ALOGD("Texture created, size = %d", size);
}
mCache.put(bitmap, texture);
} else {
texture->cleanup = true;
}
} else if (bitmap->getGenerationID() != texture->generation) {
generateTexture(bitmap, texture, true);
}
return texture;
}
Texture* TextureCache::getTransient(SkBitmap* bitmap) {
Texture* texture = new Texture();
texture->bitmapSize = bitmap->rowBytes() * bitmap->height();
texture->cleanup = true;
generateTexture(bitmap, texture, false);
return texture;
}
void TextureCache::remove(SkBitmap* bitmap) {
mCache.remove(bitmap);
}
void TextureCache::removeDeferred(SkBitmap* bitmap) {
Mutex::Autolock _l(mLock);
mGarbage.push(bitmap);
}
void TextureCache::clearGarbage() {
Mutex::Autolock _l(mLock);
size_t count = mGarbage.size();
for (size_t i = 0; i < count; i++) {
const SkBitmap* bitmap = mGarbage.itemAt(i);
mCache.remove(bitmap);
delete bitmap;
}
mGarbage.clear();
}
void TextureCache::clear() {
mCache.clear();
TEXTURE_LOGD("TextureCache:clear(), mSize = %d", mSize);
}
void TextureCache::flush() {
if (mFlushRate >= 1.0f || mCache.size() == 0) return;
if (mFlushRate <= 0.0f) {
clear();
return;
}
uint32_t targetSize = uint32_t(mSize * mFlushRate);
TEXTURE_LOGD("TextureCache::flush: target size: %d", targetSize);
while (mSize > targetSize) {
mCache.removeOldest();
}
}
void TextureCache::generateTexture(SkBitmap* bitmap, Texture* texture, bool regenerate) {
SkAutoLockPixels alp(*bitmap);
if (!bitmap->readyToDraw()) {
ALOGE("Cannot generate texture from bitmap");
return;
}
// We could also enable mipmapping if both bitmap dimensions are powers
// of 2 but we'd have to deal with size changes. Let's keep this simple
const bool canMipMap = Extensions::getInstance().hasNPot();
// If the texture had mipmap enabled but not anymore,
// force a glTexImage2D to discard the mipmap levels
const bool resize = !regenerate || bitmap->width() != int(texture->width) ||
bitmap->height() != int(texture->height) ||
(regenerate && canMipMap && texture->mipMap && !bitmap->hasHardwareMipMap());
if (!regenerate) {
glGenTextures(1, &texture->id);
}
texture->generation = bitmap->getGenerationID();
texture->width = bitmap->width();
texture->height = bitmap->height();
Caches::getInstance().bindTexture(texture->id);
switch (bitmap->getConfig()) {
case SkBitmap::kA8_Config:
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
uploadToTexture(resize, GL_ALPHA, bitmap->rowBytesAsPixels(),
texture->width, texture->height, GL_UNSIGNED_BYTE, bitmap->getPixels());
texture->blend = true;
break;
case SkBitmap::kRGB_565_Config:
glPixelStorei(GL_UNPACK_ALIGNMENT, bitmap->bytesPerPixel());
uploadToTexture(resize, GL_RGB, bitmap->rowBytesAsPixels(),
texture->width, texture->height, GL_UNSIGNED_SHORT_5_6_5, bitmap->getPixels());
texture->blend = false;
break;
case SkBitmap::kARGB_8888_Config:
glPixelStorei(GL_UNPACK_ALIGNMENT, bitmap->bytesPerPixel());
uploadToTexture(resize, GL_RGBA, bitmap->rowBytesAsPixels(),
texture->width, texture->height, GL_UNSIGNED_BYTE, bitmap->getPixels());
// Do this after calling getPixels() to make sure Skia's deferred
// decoding happened
texture->blend = !bitmap->isOpaque();
break;
case SkBitmap::kARGB_4444_Config:
case SkBitmap::kIndex8_Config:
glPixelStorei(GL_UNPACK_ALIGNMENT, bitmap->bytesPerPixel());
uploadLoFiTexture(resize, bitmap, texture->width, texture->height);
texture->blend = !bitmap->isOpaque();
break;
default:
ALOGW("Unsupported bitmap config: %d", bitmap->getConfig());
break;
}
if (canMipMap) {
texture->mipMap = bitmap->hasHardwareMipMap();
if (texture->mipMap) {
glGenerateMipmap(GL_TEXTURE_2D);
}
}
if (!regenerate) {
texture->setFilter(GL_NEAREST);
texture->setWrap(GL_CLAMP_TO_EDGE);
}
}
void TextureCache::uploadLoFiTexture(bool resize, SkBitmap* bitmap,
uint32_t width, uint32_t height) {
SkBitmap rgbaBitmap;
rgbaBitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height);
rgbaBitmap.allocPixels();
rgbaBitmap.eraseColor(0);
rgbaBitmap.setIsOpaque(bitmap->isOpaque());
SkCanvas canvas(rgbaBitmap);
canvas.drawBitmap(*bitmap, 0.0f, 0.0f, NULL);
uploadToTexture(resize, GL_RGBA, rgbaBitmap.rowBytesAsPixels(), width, height,
GL_UNSIGNED_BYTE, rgbaBitmap.getPixels());
}
void TextureCache::uploadToTexture(bool resize, GLenum format, GLsizei stride,
GLsizei width, GLsizei height, GLenum type, const GLvoid * data) {
// TODO: With OpenGL ES 2.0 we need to copy the bitmap in a temporary buffer
// if the stride doesn't match the width
const bool useStride = stride != width && Extensions::getInstance().hasUnpackRowLength();
if (useStride) {
glPixelStorei(GL_UNPACK_ROW_LENGTH, stride);
}
if (resize) {
glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, type, data);
} else {
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, format, type, data);
}
if (useStride) {
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
}
}
}; // namespace uirenderer
}; // namespace android