| /* | 
 |  * 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 |