/* * 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 "Patch.h" #include "PatchCache.h" #include "Properties.h" #include "renderstate/RenderState.h" namespace android { namespace uirenderer { /////////////////////////////////////////////////////////////////////////////// // Constructors/destructor /////////////////////////////////////////////////////////////////////////////// PatchCache::PatchCache(RenderState& renderState) : mRenderState(renderState) , mSize(0) , mCache(LruCache::kUnlimitedCapacity) , mMeshBuffer(0) , mFreeBlocks(nullptr) , mGenerationId(0) { char property[PROPERTY_VALUE_MAX]; if (property_get(PROPERTY_PATCH_CACHE_SIZE, property, nullptr) > 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() { bool created = false; if (!mMeshBuffer) { glGenBuffers(1, &mMeshBuffer); created = true; } mRenderState.meshState().bindMeshBuffer(mMeshBuffer); mRenderState.meshState().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) { mRenderState.meshState().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 = nullptr; } 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); // 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 = nullptr; break; } } LOG_ALWAYS_FATAL_IF(patch == nullptr); 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++) { 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->positionOffset, 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, nullptr, 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) { // This call ensures the VBO exists and that it is bound init(); // 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 = nullptr; 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 = nullptr; block = mFreeBlocks; } // Copy the 9patch mesh in the VBO newMesh->positionOffset = (GLintptr) (block->offset); newMesh->textureOffset = newMesh->positionOffset + kMeshTextureOffset; glBufferSubData(GL_ARRAY_BUFFER, newMesh->positionOffset, size, newMesh->vertices.get()); // 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; } static const UvMapper sIdentity; 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) { const UvMapper& mapper = entry ? entry->uvMapper : sIdentity; Patch* newMesh = new Patch(bitmapWidth, bitmapHeight, pixelWidth, pixelHeight, mapper, patch); if (newMesh->vertices) { setupMesh(newMesh); } #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->positionOffset, block->size); block = block->next; } ALOGD("%s: Free blocks%s", prefix, dump.string()); } #endif }; // namespace uirenderer }; // namespace android