/* * 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 #include #include "Caches.h" #include "PatchCache.h" #include "Properties.h" namespace android { namespace uirenderer { /////////////////////////////////////////////////////////////////////////////// // Constructors/destructor /////////////////////////////////////////////////////////////////////////////// PatchCache::PatchCache(): mSize(0), mCache(LruCache::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::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& patchesToRemove, Res_png_9patch* patch) { LruCache::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 patchesToRemove; { // scope for the mutex Mutex::Autolock _l(mLock); size_t count = mGarbage.size(); for (size_t i = 0; i < count; i++) { remove(patchesToRemove, mGarbage[i]); } 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