TextureCache.cpp revision 860d155f866cc15a725e7ce03763280987f24901
1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17#define LOG_TAG "OpenGLRenderer" 18 19#include <GLES2/gl2.h> 20 21#include <SkCanvas.h> 22 23#include <utils/Mutex.h> 24 25#include "Caches.h" 26#include "TextureCache.h" 27#include "Properties.h" 28 29namespace android { 30namespace uirenderer { 31 32/////////////////////////////////////////////////////////////////////////////// 33// Constructors/destructor 34/////////////////////////////////////////////////////////////////////////////// 35 36TextureCache::TextureCache(): 37 mCache(LruCache<const SkBitmap*, Texture*>::kUnlimitedCapacity), 38 mSize(0), mMaxSize(MB(DEFAULT_TEXTURE_CACHE_SIZE)), 39 mFlushRate(DEFAULT_TEXTURE_CACHE_FLUSH_RATE) { 40 char property[PROPERTY_VALUE_MAX]; 41 if (property_get(PROPERTY_TEXTURE_CACHE_SIZE, property, NULL) > 0) { 42 INIT_LOGD(" Setting texture cache size to %sMB", property); 43 setMaxSize(MB(atof(property))); 44 } else { 45 INIT_LOGD(" Using default texture cache size of %.2fMB", DEFAULT_TEXTURE_CACHE_SIZE); 46 } 47 48 if (property_get(PROPERTY_TEXTURE_CACHE_FLUSH_RATE, property, NULL) > 0) { 49 float flushRate = atof(property); 50 INIT_LOGD(" Setting texture cache flush rate to %.2f%%", flushRate * 100.0f); 51 setFlushRate(flushRate); 52 } else { 53 INIT_LOGD(" Using default texture cache flush rate of %.2f%%", 54 DEFAULT_TEXTURE_CACHE_FLUSH_RATE * 100.0f); 55 } 56 57 init(); 58} 59 60TextureCache::TextureCache(uint32_t maxByteSize): 61 mCache(LruCache<const SkBitmap*, Texture*>::kUnlimitedCapacity), 62 mSize(0), mMaxSize(maxByteSize) { 63 init(); 64} 65 66TextureCache::~TextureCache() { 67 mCache.clear(); 68} 69 70void TextureCache::init() { 71 mCache.setOnEntryRemovedListener(this); 72 73 glGetIntegerv(GL_MAX_TEXTURE_SIZE, &mMaxTextureSize); 74 INIT_LOGD(" Maximum texture dimension is %d pixels", mMaxTextureSize); 75 76 mDebugEnabled = readDebugLevel() & kDebugCaches; 77} 78 79/////////////////////////////////////////////////////////////////////////////// 80// Size management 81/////////////////////////////////////////////////////////////////////////////// 82 83uint32_t TextureCache::getSize() { 84 return mSize; 85} 86 87uint32_t TextureCache::getMaxSize() { 88 return mMaxSize; 89} 90 91void TextureCache::setMaxSize(uint32_t maxSize) { 92 mMaxSize = maxSize; 93 while (mSize > mMaxSize) { 94 mCache.removeOldest(); 95 } 96} 97 98void TextureCache::setFlushRate(float flushRate) { 99 mFlushRate = fmaxf(0.0f, fminf(1.0f, flushRate)); 100} 101 102/////////////////////////////////////////////////////////////////////////////// 103// Callbacks 104/////////////////////////////////////////////////////////////////////////////// 105 106void TextureCache::operator()(const SkBitmap*&, Texture*& texture) { 107 // This will be called already locked 108 if (texture) { 109 mSize -= texture->bitmapSize; 110 TEXTURE_LOGD("TextureCache::callback: name, removed size, mSize = %d, %d, %d", 111 texture->id, texture->bitmapSize, mSize); 112 if (mDebugEnabled) { 113 ALOGD("Texture deleted, size = %d", texture->bitmapSize); 114 } 115 texture->deleteTexture(); 116 delete texture; 117 } 118} 119 120/////////////////////////////////////////////////////////////////////////////// 121// Caching 122/////////////////////////////////////////////////////////////////////////////// 123 124void TextureCache::resetMarkInUse() { 125 LruCache<const SkBitmap*, Texture*>::Iterator iter(mCache); 126 while (iter.next()) { 127 iter.value()->isInUse = false; 128 } 129} 130 131bool TextureCache::canMakeTextureFromBitmap(const SkBitmap* bitmap) { 132 if (bitmap->width() > mMaxTextureSize || bitmap->height() > mMaxTextureSize) { 133 ALOGW("Bitmap too large to be uploaded into a texture (%dx%d, max=%dx%d)", 134 bitmap->width(), bitmap->height(), mMaxTextureSize, mMaxTextureSize); 135 return false; 136 } 137 return true; 138} 139 140// Returns a prepared Texture* that either is already in the cache or can fit 141// in the cache (and is thus added to the cache) 142Texture* TextureCache::getCachedTexture(const SkBitmap* bitmap) { 143 Texture* texture = mCache.get(bitmap); 144 145 if (!texture) { 146 if (!canMakeTextureFromBitmap(bitmap)) { 147 return NULL; 148 } 149 150 const uint32_t size = bitmap->rowBytes() * bitmap->height(); 151 bool canCache = size < mMaxSize; 152 // Don't even try to cache a bitmap that's bigger than the cache 153 while (canCache && mSize + size > mMaxSize) { 154 Texture* oldest = mCache.peekOldestValue(); 155 if (oldest && !oldest->isInUse) { 156 mCache.removeOldest(); 157 } else { 158 canCache = false; 159 } 160 } 161 162 if (canCache) { 163 texture = new Texture(); 164 texture->bitmapSize = size; 165 generateTexture(bitmap, texture, false); 166 167 mSize += size; 168 TEXTURE_LOGD("TextureCache::get: create texture(%p): name, size, mSize = %d, %d, %d", 169 bitmap, texture->id, size, mSize); 170 if (mDebugEnabled) { 171 ALOGD("Texture created, size = %d", size); 172 } 173 mCache.put(bitmap, texture); 174 } 175 } else if (!texture->isInUse && bitmap->getGenerationID() != texture->generation) { 176 // Texture was in the cache but is dirty, re-upload 177 // TODO: Re-adjust the cache size if the bitmap's dimensions have changed 178 generateTexture(bitmap, texture, true); 179 } 180 181 return texture; 182} 183 184bool TextureCache::prefetchAndMarkInUse(const SkBitmap* bitmap) { 185 Texture* texture = getCachedTexture(bitmap); 186 if (texture) { 187 texture->isInUse = true; 188 } 189 return texture; 190} 191 192Texture* TextureCache::get(const SkBitmap* bitmap) { 193 Texture* texture = getCachedTexture(bitmap); 194 195 if (!texture) { 196 if (!canMakeTextureFromBitmap(bitmap)) { 197 return NULL; 198 } 199 200 const uint32_t size = bitmap->rowBytes() * bitmap->height(); 201 texture = new Texture(); 202 texture->bitmapSize = size; 203 generateTexture(bitmap, texture, false); 204 texture->cleanup = true; 205 } 206 207 return texture; 208} 209 210Texture* TextureCache::getTransient(const SkBitmap* bitmap) { 211 Texture* texture = new Texture(); 212 texture->bitmapSize = bitmap->rowBytes() * bitmap->height(); 213 texture->cleanup = true; 214 215 generateTexture(bitmap, texture, false); 216 217 return texture; 218} 219 220void TextureCache::remove(const SkBitmap* bitmap) { 221 mCache.remove(bitmap); 222} 223 224void TextureCache::removeDeferred(const SkBitmap* bitmap) { 225 Mutex::Autolock _l(mLock); 226 mGarbage.push(bitmap); 227} 228 229void TextureCache::clearGarbage() { 230 Mutex::Autolock _l(mLock); 231 size_t count = mGarbage.size(); 232 for (size_t i = 0; i < count; i++) { 233 const SkBitmap* bitmap = mGarbage.itemAt(i); 234 mCache.remove(bitmap); 235 delete bitmap; 236 } 237 mGarbage.clear(); 238} 239 240void TextureCache::clear() { 241 mCache.clear(); 242 TEXTURE_LOGD("TextureCache:clear(), mSize = %d", mSize); 243} 244 245void TextureCache::flush() { 246 if (mFlushRate >= 1.0f || mCache.size() == 0) return; 247 if (mFlushRate <= 0.0f) { 248 clear(); 249 return; 250 } 251 252 uint32_t targetSize = uint32_t(mSize * mFlushRate); 253 TEXTURE_LOGD("TextureCache::flush: target size: %d", targetSize); 254 255 while (mSize > targetSize) { 256 mCache.removeOldest(); 257 } 258} 259 260void TextureCache::generateTexture(const SkBitmap* bitmap, Texture* texture, bool regenerate) { 261 SkAutoLockPixels alp(*bitmap); 262 263 if (!bitmap->readyToDraw()) { 264 ALOGE("Cannot generate texture from bitmap"); 265 return; 266 } 267 268 // We could also enable mipmapping if both bitmap dimensions are powers 269 // of 2 but we'd have to deal with size changes. Let's keep this simple 270 const bool canMipMap = Extensions::getInstance().hasNPot(); 271 272 // If the texture had mipmap enabled but not anymore, 273 // force a glTexImage2D to discard the mipmap levels 274 const bool resize = !regenerate || bitmap->width() != int(texture->width) || 275 bitmap->height() != int(texture->height) || 276 (regenerate && canMipMap && texture->mipMap && !bitmap->hasHardwareMipMap()); 277 278 if (!regenerate) { 279 glGenTextures(1, &texture->id); 280 } 281 282 texture->generation = bitmap->getGenerationID(); 283 texture->width = bitmap->width(); 284 texture->height = bitmap->height(); 285 286 Caches::getInstance().bindTexture(texture->id); 287 288 switch (bitmap->config()) { 289 case SkBitmap::kA8_Config: 290 glPixelStorei(GL_UNPACK_ALIGNMENT, 1); 291 uploadToTexture(resize, GL_ALPHA, bitmap->rowBytesAsPixels(), 292 texture->width, texture->height, GL_UNSIGNED_BYTE, bitmap->getPixels()); 293 texture->blend = true; 294 break; 295 case SkBitmap::kRGB_565_Config: 296 glPixelStorei(GL_UNPACK_ALIGNMENT, bitmap->bytesPerPixel()); 297 uploadToTexture(resize, GL_RGB, bitmap->rowBytesAsPixels(), 298 texture->width, texture->height, GL_UNSIGNED_SHORT_5_6_5, bitmap->getPixels()); 299 texture->blend = false; 300 break; 301 case SkBitmap::kARGB_8888_Config: 302 glPixelStorei(GL_UNPACK_ALIGNMENT, bitmap->bytesPerPixel()); 303 uploadToTexture(resize, GL_RGBA, bitmap->rowBytesAsPixels(), 304 texture->width, texture->height, GL_UNSIGNED_BYTE, bitmap->getPixels()); 305 // Do this after calling getPixels() to make sure Skia's deferred 306 // decoding happened 307 texture->blend = !bitmap->isOpaque(); 308 break; 309 case SkBitmap::kARGB_4444_Config: 310 case SkBitmap::kIndex8_Config: 311 glPixelStorei(GL_UNPACK_ALIGNMENT, bitmap->bytesPerPixel()); 312 uploadLoFiTexture(resize, bitmap, texture->width, texture->height); 313 texture->blend = !bitmap->isOpaque(); 314 break; 315 default: 316 ALOGW("Unsupported bitmap config: %d", bitmap->config()); 317 break; 318 } 319 320 if (canMipMap) { 321 texture->mipMap = bitmap->hasHardwareMipMap(); 322 if (texture->mipMap) { 323 glGenerateMipmap(GL_TEXTURE_2D); 324 } 325 } 326 327 if (!regenerate) { 328 texture->setFilter(GL_NEAREST); 329 texture->setWrap(GL_CLAMP_TO_EDGE); 330 } 331} 332 333void TextureCache::uploadLoFiTexture(bool resize, const SkBitmap* bitmap, 334 uint32_t width, uint32_t height) { 335 SkBitmap rgbaBitmap; 336 rgbaBitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height, 0, bitmap->alphaType()); 337 rgbaBitmap.allocPixels(); 338 rgbaBitmap.eraseColor(0); 339 340 SkCanvas canvas(rgbaBitmap); 341 canvas.drawBitmap(*bitmap, 0.0f, 0.0f, NULL); 342 343 uploadToTexture(resize, GL_RGBA, rgbaBitmap.rowBytesAsPixels(), width, height, 344 GL_UNSIGNED_BYTE, rgbaBitmap.getPixels()); 345} 346 347void TextureCache::uploadToTexture(bool resize, GLenum format, GLsizei stride, 348 GLsizei width, GLsizei height, GLenum type, const GLvoid * data) { 349 // TODO: With OpenGL ES 2.0 we need to copy the bitmap in a temporary buffer 350 // if the stride doesn't match the width 351 const bool useStride = stride != width && Extensions::getInstance().hasUnpackRowLength(); 352 if (useStride) { 353 glPixelStorei(GL_UNPACK_ROW_LENGTH, stride); 354 } 355 356 if (resize) { 357 glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, type, data); 358 } else { 359 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, format, type, data); 360 } 361 362 if (useStride) { 363 glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); 364 } 365} 366 367}; // namespace uirenderer 368}; // namespace android 369