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