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