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
PatchCache(RenderState & renderState)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
~PatchCache()42 PatchCache::~PatchCache() {
43 clear();
44 }
45
init()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
hash() const65 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
compare(const PatchCache::PatchDescription & lhs,const PatchCache::PatchDescription & rhs)74 int PatchCache::PatchDescription::compare(const PatchCache::PatchDescription& lhs,
75 const PatchCache::PatchDescription& rhs) {
76 return memcmp(&lhs, &rhs, sizeof(PatchDescription));
77 }
78
clear()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
clearCache()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
remove(Vector<patch_pair_t> & patchesToRemove,Res_png_9patch * patch)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
removeDeferred(Res_png_9patch * patch)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
clearGarbage()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
createVertexBuffer()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 */
setupMesh(Patch * newMesh)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
get(const AssetAtlas::Entry * entry,const uint32_t bitmapWidth,const uint32_t bitmapHeight,const float pixelWidth,const float pixelHeight,const Res_png_9patch * patch)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
dumpFreeBlocks(const char * prefix)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