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