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 <utils/JenkinsHash.h> 20#include <utils/Log.h> 21 22#include "Caches.h" 23#include "Patch.h" 24#include "PatchCache.h" 25#include "Properties.h" 26#include "renderstate/RenderState.h" 27 28namespace android { 29namespace uirenderer { 30 31/////////////////////////////////////////////////////////////////////////////// 32// Constructors/destructor 33/////////////////////////////////////////////////////////////////////////////// 34 35PatchCache::PatchCache(RenderState& renderState) 36 : mRenderState(renderState) 37 , mSize(0) 38 , mCache(LruCache<PatchDescription, Patch*>::kUnlimitedCapacity) 39 , mMeshBuffer(0) 40 , mFreeBlocks(nullptr) 41 , mGenerationId(0) { 42 char property[PROPERTY_VALUE_MAX]; 43 if (property_get(PROPERTY_PATCH_CACHE_SIZE, property, nullptr) > 0) { 44 INIT_LOGD(" Setting patch cache size to %skB", property); 45 mMaxSize = KB(atoi(property)); 46 } else { 47 INIT_LOGD(" Using default patch cache size of %.2fkB", DEFAULT_PATCH_CACHE_SIZE); 48 mMaxSize = KB(DEFAULT_PATCH_CACHE_SIZE); 49 } 50} 51 52PatchCache::~PatchCache() { 53 clear(); 54} 55 56void PatchCache::init() { 57 bool created = false; 58 if (!mMeshBuffer) { 59 glGenBuffers(1, &mMeshBuffer); 60 created = true; 61 } 62 63 mRenderState.meshState().bindMeshBuffer(mMeshBuffer); 64 mRenderState.meshState().resetVertexPointers(); 65 66 if (created) { 67 createVertexBuffer(); 68 } 69} 70 71/////////////////////////////////////////////////////////////////////////////// 72// Caching 73/////////////////////////////////////////////////////////////////////////////// 74 75hash_t PatchCache::PatchDescription::hash() const { 76 uint32_t hash = JenkinsHashMix(0, android::hash_type(mPatch)); 77 hash = JenkinsHashMix(hash, mBitmapWidth); 78 hash = JenkinsHashMix(hash, mBitmapHeight); 79 hash = JenkinsHashMix(hash, mPixelWidth); 80 hash = JenkinsHashMix(hash, mPixelHeight); 81 return JenkinsHashWhiten(hash); 82} 83 84int PatchCache::PatchDescription::compare(const PatchCache::PatchDescription& lhs, 85 const PatchCache::PatchDescription& rhs) { 86 return memcmp(&lhs, &rhs, sizeof(PatchDescription)); 87} 88 89void PatchCache::clear() { 90 clearCache(); 91 92 if (mMeshBuffer) { 93 mRenderState.meshState().unbindMeshBuffer(); 94 glDeleteBuffers(1, &mMeshBuffer); 95 mMeshBuffer = 0; 96 mSize = 0; 97 } 98} 99 100void PatchCache::clearCache() { 101 LruCache<PatchDescription, Patch*>::Iterator i(mCache); 102 while (i.next()) { 103 delete i.value(); 104 } 105 mCache.clear(); 106 107 BufferBlock* block = mFreeBlocks; 108 while (block) { 109 BufferBlock* next = block->next; 110 delete block; 111 block = next; 112 } 113 mFreeBlocks = nullptr; 114} 115 116void PatchCache::remove(Vector<patch_pair_t>& patchesToRemove, Res_png_9patch* patch) { 117 LruCache<PatchDescription, Patch*>::Iterator i(mCache); 118 while (i.next()) { 119 const PatchDescription& key = i.key(); 120 if (key.getPatch() == patch) { 121 patchesToRemove.push(patch_pair_t(&key, i.value())); 122 } 123 } 124} 125 126void PatchCache::removeDeferred(Res_png_9patch* patch) { 127 Mutex::Autolock _l(mLock); 128 129 // Assert that patch is not already garbage 130 size_t count = mGarbage.size(); 131 for (size_t i = 0; i < count; i++) { 132 if (patch == mGarbage[i]) { 133 patch = nullptr; 134 break; 135 } 136 } 137 LOG_ALWAYS_FATAL_IF(patch == nullptr); 138 139 mGarbage.push(patch); 140} 141 142void PatchCache::clearGarbage() { 143 Vector<patch_pair_t> patchesToRemove; 144 145 { // scope for the mutex 146 Mutex::Autolock _l(mLock); 147 size_t count = mGarbage.size(); 148 for (size_t i = 0; i < count; i++) { 149 Res_png_9patch* patch = mGarbage[i]; 150 remove(patchesToRemove, patch); 151 // A Res_png_9patch is actually an array of byte that's larger 152 // than sizeof(Res_png_9patch). It must be freed as an array. 153 delete[] (int8_t*) patch; 154 } 155 mGarbage.clear(); 156 } 157 158 // TODO: We could sort patchesToRemove by offset to merge 159 // adjacent free blocks 160 for (size_t i = 0; i < patchesToRemove.size(); i++) { 161 const patch_pair_t& pair = patchesToRemove[i]; 162 163 // Release the patch and mark the space in the free list 164 Patch* patch = pair.getSecond(); 165 BufferBlock* block = new BufferBlock(patch->positionOffset, patch->getSize()); 166 block->next = mFreeBlocks; 167 mFreeBlocks = block; 168 169 mSize -= patch->getSize(); 170 171 mCache.remove(*pair.getFirst()); 172 delete patch; 173 } 174 175#if DEBUG_PATCHES 176 if (patchesToRemove.size() > 0) { 177 dumpFreeBlocks("Removed garbage"); 178 } 179#endif 180} 181 182void PatchCache::createVertexBuffer() { 183 glBufferData(GL_ARRAY_BUFFER, mMaxSize, nullptr, GL_DYNAMIC_DRAW); 184 mSize = 0; 185 mFreeBlocks = new BufferBlock(0, mMaxSize); 186 mGenerationId++; 187} 188 189/** 190 * Sets the mesh's offsets and copies its associated vertices into 191 * the mesh buffer (VBO). 192 */ 193void PatchCache::setupMesh(Patch* newMesh) { 194 // This call ensures the VBO exists and that it is bound 195 init(); 196 197 // If we're running out of space, let's clear the entire cache 198 uint32_t size = newMesh->getSize(); 199 if (mSize + size > mMaxSize) { 200 clearCache(); 201 createVertexBuffer(); 202 } 203 204 // Find a block where we can fit the mesh 205 BufferBlock* previous = nullptr; 206 BufferBlock* block = mFreeBlocks; 207 while (block) { 208 // The mesh fits 209 if (block->size >= size) { 210 break; 211 } 212 previous = block; 213 block = block->next; 214 } 215 216 // We have enough space left in the buffer, but it's 217 // too fragmented, let's clear the cache 218 if (!block) { 219 clearCache(); 220 createVertexBuffer(); 221 previous = nullptr; 222 block = mFreeBlocks; 223 } 224 225 // Copy the 9patch mesh in the VBO 226 newMesh->positionOffset = (GLintptr) (block->offset); 227 newMesh->textureOffset = newMesh->positionOffset + kMeshTextureOffset; 228 glBufferSubData(GL_ARRAY_BUFFER, newMesh->positionOffset, size, newMesh->vertices.get()); 229 230 // Remove the block since we've used it entirely 231 if (block->size == size) { 232 if (previous) { 233 previous->next = block->next; 234 } else { 235 mFreeBlocks = block->next; 236 } 237 delete block; 238 } else { 239 // Resize the block now that it's occupied 240 block->offset += size; 241 block->size -= size; 242 } 243 244 mSize += size; 245} 246 247static const UvMapper sIdentity; 248 249const Patch* PatchCache::get(const AssetAtlas::Entry* entry, 250 const uint32_t bitmapWidth, const uint32_t bitmapHeight, 251 const float pixelWidth, const float pixelHeight, const Res_png_9patch* patch) { 252 253 const PatchDescription description(bitmapWidth, bitmapHeight, pixelWidth, pixelHeight, patch); 254 const Patch* mesh = mCache.get(description); 255 256 if (!mesh) { 257 const UvMapper& mapper = entry ? entry->uvMapper : sIdentity; 258 Patch* newMesh = new Patch(bitmapWidth, bitmapHeight, 259 pixelWidth, pixelHeight, mapper, patch); 260 261 if (newMesh->vertices) { 262 setupMesh(newMesh); 263 } 264 265#if DEBUG_PATCHES 266 dumpFreeBlocks("Adding patch"); 267#endif 268 269 mCache.put(description, newMesh); 270 return newMesh; 271 } 272 273 return mesh; 274} 275 276#if DEBUG_PATCHES 277void PatchCache::dumpFreeBlocks(const char* prefix) { 278 String8 dump; 279 BufferBlock* block = mFreeBlocks; 280 while (block) { 281 dump.appendFormat("->(%d, %d)", block->positionOffset, block->size); 282 block = block->next; 283 } 284 ALOGD("%s: Free blocks%s", prefix, dump.string()); 285} 286#endif 287 288}; // namespace uirenderer 289}; // namespace android 290